milestone: desktop path Phases 1-5

Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests
Phase 2 (Wayland Compositor): bounded scaffold, zero warnings
Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick)
Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker
Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker

Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
2026-04-29 09:54:06 +01:00
parent b23714f542
commit 8acc73d774
508 changed files with 76526 additions and 396 deletions
@@ -0,0 +1,55 @@
ENTRY(_start)
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
@@ -0,0 +1,53 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
// Setup a stack.
ldr x8, ={number}
ldr x0, ={fd}
ldr x1, ={map} // pointer to Map struct
ldr x2, ={map_size} // size of Map struct
svc 0
// Failure if return value is zero
cbz x0, 1f
// Failure if return value is negative
tbnz x0, 63, 1f
// Set up stack frame
mov sp, x0
add sp, sp, #{stack_size}
mov fp, sp
// Stack has the same alignment as `size`.
bl start
// `start` must never return.
// failure, emit undefined instruction
1:
udf #0
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
+354
View File
@@ -0,0 +1,354 @@
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::str::FromStr;
use hashbrown::HashMap;
use redox_scheme::Socket;
use syscall::CallFlags;
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::{O_CLOEXEC, O_RDONLY, O_STAT};
use syscall::{EINTR, Error};
use redox_rt::proc::*;
use crate::KernelSchemeMap;
struct Logger;
impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &log::Record) {
let file = record.file().unwrap_or("");
let line = record.line().unwrap_or(0);
let level = record.level();
let msg = record.args();
let _ = syscall::write(
1,
alloc::format!("[{file}:{line} {level}] {msg}\n").as_bytes(),
);
}
fn flush(&self) {}
}
const KERNEL_METADATA_BASE: usize = crate::arch::USERMODE_END - syscall::KERNEL_METADATA_SIZE;
pub fn main() -> ! {
let mut cursor = KERNEL_METADATA_BASE;
let kernel_scheme_infos = unsafe {
let base_ptr = cursor as *const u8;
let infos_len = *(base_ptr as *const usize);
let infos_ptr = base_ptr.add(core::mem::size_of::<usize>()) as *const KernelSchemeInfo;
let slice = core::slice::from_raw_parts(infos_ptr, infos_len);
cursor += core::mem::size_of::<usize>() // kernel scheme number size
+ infos_len // kernel scheme number
* core::mem::size_of::<KernelSchemeInfo>();
slice
};
let scheme_creation_cap = unsafe {
let base_ptr = cursor as *const u8;
FdGuard::new(*(base_ptr as *const usize))
};
let mut kernel_schemes = KernelSchemeMap::new(kernel_scheme_infos);
let auth = kernel_schemes
.0
.remove(&GlobalSchemes::Proc)
.expect("failed to get proc fd");
let this_thr_fd = auth
.dup(b"cur-context")
.expect("failed to open open_via_dup")
.to_upper()
.unwrap();
let this_thr_fd = unsafe { redox_rt::initialize_freestanding(this_thr_fd) };
let mut env_bytes = [0_u8; 4096];
let mut envs = {
let fd = FdGuard::new(
syscall::openat(
kernel_schemes
.get(GlobalSchemes::Sys)
.expect("failed to get sys fd")
.as_raw_fd(),
"env",
O_RDONLY | O_CLOEXEC,
0,
)
.expect("bootstrap: failed to open env"),
);
let bytes_read = fd
.read(&mut env_bytes)
.expect("bootstrap: failed to read env");
if bytes_read >= env_bytes.len() {
// TODO: Handle this, we can allocate as much as we want in theory.
panic!("env is too large");
}
let env_bytes = &mut env_bytes[..bytes_read];
env_bytes
.split(|&c| c == b'\n')
.filter(|var| !var.is_empty())
.filter(|var| !var.starts_with(b"INITFS_"))
.collect::<Vec<_>>()
};
envs.push(b"RUST_BACKTRACE=1");
//envs.push(b"LD_DEBUG=all");
envs.push(b"LD_LIBRARY_PATH=/scheme/initfs/lib");
log::set_max_level(log::LevelFilter::Warn);
if let Some(log_env) = envs
.iter()
.find_map(|var| var.strip_prefix(b"BOOTSTRAP_LOG_LEVEL="))
{
if let Ok(Ok(log_level)) = str::from_utf8(&log_env).map(|s| log::LevelFilter::from_str(s)) {
log::set_max_level(log_level);
}
}
let _ = log::set_logger(&Logger);
unsafe extern "C" {
// The linker script will define this as the location of the initfs header.
static __initfs_header: u8;
// The linker script will define this as the end of the executable (excluding initfs).
static __bss_end: u8;
}
let initfs_start = core::ptr::addr_of!(__initfs_header);
let initfs_length = unsafe {
(*(core::ptr::addr_of!(__initfs_header) as *const redox_initfs::types::Header))
.initfs_size
.get() as usize
};
let (scheme_creation_cap, auth, kernel_schemes, initfs_fd) = spawn(
"initfs daemon",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
false,
|write_fd, socket, _, _| unsafe {
crate::initfs::run(
core::slice::from_raw_parts(initfs_start, initfs_length),
write_fd,
socket,
);
},
);
// Unmap initfs data as only the initfs scheme implementation needs it.
unsafe {
let executable_end = core::ptr::addr_of!(__bss_end)
.add(core::ptr::addr_of!(__bss_end).align_offset(syscall::PAGE_SIZE));
syscall::funmap(
executable_end as usize,
initfs_length.next_multiple_of(syscall::PAGE_SIZE)
- (executable_end.offset_from(initfs_start) as usize),
)
.unwrap();
}
let (scheme_creation_cap, auth, kernel_schemes, proc_fd) = spawn(
"process manager",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
true,
|write_fd, socket, auth, mut kernel_schemes| {
let event = kernel_schemes
.0
.remove(&GlobalSchemes::Event)
.expect("failed to get event fd");
drop(kernel_schemes);
crate::procmgr::run(write_fd, socket, auth, event)
},
);
let scheme_creation_cap_dup = scheme_creation_cap
.dup(b"")
.expect("failed to dup scheme creation cap");
let (_, _, _, initns_fd) = spawn(
"init namespace manager",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
false,
|write_fd, socket, _, kernel_schemes| {
let mut schemes = HashMap::default();
for (scheme, fd) in kernel_schemes.0.into_iter() {
schemes.insert(scheme.as_str().to_string(), Arc::new(fd));
}
schemes.insert(
"proc".to_string(),
// A bit dirty, but necessary as the parent process still needs access to it. Rust
// doesn't know that the fd got cloned by fork.
Arc::new(FdGuard::new(proc_fd.as_raw_fd())),
);
schemes.insert("initfs".to_string(), Arc::new(initfs_fd));
crate::initnsmgr::run(write_fd, socket, schemes, scheme_creation_cap_dup)
},
);
let (init_proc_fd, init_thr_fd) = unsafe { make_init(proc_fd.take()) };
// from this point, this_thr_fd is no longer valid
const CWD: &[u8] = b"/scheme/initfs";
let cwd_fd = FdGuard::new(
syscall::openat(initns_fd.as_raw_fd(), "/scheme/initfs", O_STAT, 0)
.expect("failed to open cwd fd"),
)
.to_upper()
.unwrap();
let extrainfo = ExtraInfo {
cwd: Some(CWD),
sigprocmask: 0,
sigignmask: 0,
umask: redox_rt::sys::get_umask(),
thr_fd: init_thr_fd.as_raw_fd(),
proc_fd: init_proc_fd.as_raw_fd(),
ns_fd: Some(initns_fd.take()),
cwd_fd: Some(cwd_fd.as_raw_fd()),
};
let exe_path = "/scheme/initfs/bin/init";
let image_file = FdGuard::new(
syscall::openat(extrainfo.ns_fd.unwrap(), exe_path, O_RDONLY | O_CLOEXEC, 0)
.expect("failed to open init"),
)
.to_upper()
.unwrap();
let FexecResult::Interp {
path: interp_path,
interp_override,
} = fexec_impl(
image_file,
init_thr_fd,
init_proc_fd,
exe_path.as_bytes(),
&[exe_path.as_bytes()],
&envs,
&extrainfo,
None,
)
.expect("failed to execute init");
// According to elf(5), PT_INTERP requires that the interpreter path be
// null-terminated. Violating this should therefore give the "format error" ENOEXEC.
let interp_cstr = CStr::from_bytes_with_nul(&interp_path).expect("interpreter not valid C str");
let interp_file = FdGuard::new(
syscall::openat(
extrainfo.ns_fd.unwrap(), // initns, not initfs!
interp_cstr.to_str().expect("interpreter not UTF-8"),
O_RDONLY | O_CLOEXEC,
0,
)
.expect("failed to open dynamic linker"),
)
.to_upper()
.unwrap();
fexec_impl(
interp_file,
init_thr_fd,
init_proc_fd,
exe_path.as_bytes(),
&[exe_path.as_bytes()],
&envs,
&extrainfo,
Some(interp_override),
)
.expect("failed to execute init");
unreachable!()
}
pub(crate) fn spawn(
name: &str,
auth: FdGuard,
this_thr_fd: &FdGuardUpper,
scheme_creation_cap: FdGuard,
kernel_schemes: KernelSchemeMap,
nonblock: bool,
inner: impl FnOnce(FdGuard, Socket, FdGuard, KernelSchemeMap) -> !,
) -> (FdGuard, FdGuard, KernelSchemeMap, FdGuard) {
let read = FdGuard::new(
syscall::openat(
kernel_schemes
.get(GlobalSchemes::Pipe)
.expect("failed to get pipe fd")
.as_raw_fd(),
"",
O_CLOEXEC,
0,
)
.expect("failed to open sync read pipe"),
);
// The write pipe will not inherit O_CLOEXEC, but is closed by the daemon later.
let write = FdGuard::new(
syscall::dup(read.as_raw_fd(), b"write").expect("failed to open sync write pipe"),
);
match fork_impl(&ForkArgs::Init {
this_thr_fd,
auth: &auth,
}) {
Err(err) => {
panic!("Failed to fork in order to start {name}: {err}");
}
// Continue serving the scheme as the child.
Ok(0) => {
drop(read);
let socket = Socket::create_inner(scheme_creation_cap.as_raw_fd(), nonblock)
.expect("failed to open proc scheme socket");
drop(scheme_creation_cap);
inner(write, socket, auth, kernel_schemes)
}
// Return in order to execute init, as the parent.
Ok(_) => {
drop(write);
let mut new_fd = usize::MAX;
let fd_bytes = unsafe {
core::slice::from_raw_parts_mut(
core::slice::from_mut(&mut new_fd).as_mut_ptr() as *mut u8,
core::mem::size_of::<usize>(),
)
};
loop {
match syscall::call_ro(
read.as_raw_fd(),
fd_bytes,
CallFlags::FD | CallFlags::FD_UPPER,
&[],
) {
Err(Error { errno: EINTR }) => continue,
_ => break,
}
}
(
scheme_creation_cap,
auth,
kernel_schemes,
FdGuard::new(new_fd),
)
}
}
}
+55
View File
@@ -0,0 +1,55 @@
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
+55
View File
@@ -0,0 +1,55 @@
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
+49
View File
@@ -0,0 +1,49 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 0x8000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
mov eax, {number}
mov ebx, {fd}
mov ecx, offset {map} # pointer to Map struct
mov edx, {map_size} # size of Map struct
int 0x80
# Test for success (nonzero value).
cmp eax, 0
jg 1f
# (failure)
ud2
1:
# Subtract 16 since all instructions seem to hate non-canonical ESP values :)
lea esp, [eax+{stack_size}-16]
mov ebp, esp
# Stack has the same alignment as `size`.
call start
# `start` must never return.
ud2
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
+485
View File
@@ -0,0 +1,485 @@
use core::convert::TryFrom;
#[allow(deprecated)]
use core::hash::{BuildHasherDefault, SipHasher};
use core::str;
use alloc::string::String;
use hashbrown::HashMap;
use redox_initfs::{InitFs, Inode, InodeDir, InodeKind, InodeStruct};
use redox_rt::proc::FdGuard;
use redox_scheme::{
CallerCtx, OpenResult, RequestKind,
scheme::{SchemeState, SchemeSync},
};
use redox_scheme::{SignalBehavior, Socket};
use syscall::PAGE_SIZE;
use syscall::data::Stat;
use syscall::dirent::DirEntry;
use syscall::dirent::DirentBuf;
use syscall::dirent::DirentKind;
use syscall::error::*;
use syscall::flag::*;
use syscall::schemev2::NewFdFlags;
enum Handle {
Node(Node),
SchemeRoot,
}
impl Handle {
fn as_node(&self) -> Result<&Node> {
match self {
Handle::Node(n) => Ok(n),
_ => Err(Error::new(EBADF)),
}
}
fn as_node_mut(&mut self) -> Result<&mut Node> {
match self {
Handle::Node(n) => Ok(n),
_ => Err(Error::new(EBADF)),
}
}
}
struct Node {
inode: Inode,
// TODO: Any better way to implement fpath? Or maybe work around it, e.g. by giving paths such
// as `initfs:__inodes__/<inode>`?
filename: String,
}
pub struct InitFsScheme {
#[allow(deprecated)]
handles: HashMap<usize, Handle, BuildHasherDefault<SipHasher>>,
next_id: usize,
fs: InitFs<'static>,
}
impl InitFsScheme {
pub fn new(bytes: &'static [u8]) -> Self {
Self {
handles: HashMap::default(),
next_id: 0,
fs: InitFs::new(bytes, Some(PAGE_SIZE.try_into().unwrap()))
.expect("failed to parse initfs"),
}
}
fn get_inode(fs: &InitFs<'static>, inode: Inode) -> Result<InodeStruct<'static>> {
fs.get_inode(inode).ok_or_else(|| Error::new(EIO))
}
fn next_id(&mut self) -> usize {
assert_ne!(self.next_id, usize::MAX, "usize overflow in initfs scheme");
self.next_id += 1;
self.next_id
}
}
struct Iter {
dir: InodeDir<'static>,
idx: u32,
}
impl Iterator for Iter {
type Item = Result<redox_initfs::Entry<'static>>;
fn next(&mut self) -> Option<Self::Item> {
let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO));
self.idx += 1;
entry.transpose()
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.dir.entry_count().ok() {
Some(size) => {
let size =
usize::try_from(size).expect("expected u32 to be convertible into usize");
(size, Some(size))
}
None => (0, None),
}
}
}
fn inode_len(inode: InodeStruct<'static>) -> Result<usize> {
Ok(match inode.kind() {
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?.len(),
InodeKind::Dir(dir) => (Iter { dir, idx: 0 }).fold(0, |len, entry| {
len + entry
.and_then(|entry| entry.name().map_err(|_| Error::new(EIO)))
.map_or(0, |name| name.len() + 1)
}),
InodeKind::Link(link) => link.data().map_err(|_| Error::new(EIO))?.len(),
InodeKind::Unknown => return Err(Error::new(EIO)),
})
}
impl SchemeSync for InitFsScheme {
fn openat(
&mut self,
dirfd: usize,
path: &str,
flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if !matches!(
self.handles.get(&dirfd).ok_or(Error::new(EBADF))?,
Handle::SchemeRoot
) {
return Err(Error::new(EACCES));
}
let mut components = path
// trim leading and trailing slash
.trim_matches('/')
// divide into components
.split('/')
// filter out double slashes (e.g. /usr//bin/...)
.filter(|c| !c.is_empty());
let mut current_inode = self.fs.root_inode();
while let Some(component) = components.next() {
match component {
"." => continue,
".." => {
let _ = components.next_back();
continue;
}
_ => (),
}
let current_inode_struct = Self::get_inode(&self.fs, current_inode)?;
let dir = match current_inode_struct.kind() {
InodeKind::Dir(dir) => dir,
// TODO: Support symlinks in other position than xopen target
InodeKind::Link(_) => {
return Err(Error::new(EOPNOTSUPP));
}
// If we still have more components in the path, and the file tree for that
// particular branch is not all directories except the last, then that file cannot
// exist.
InodeKind::File(_) | InodeKind::Unknown => return Err(Error::new(ENOENT)),
};
let mut entries = Iter { dir, idx: 0 };
current_inode = loop {
let entry_res = match entries.next() {
Some(e) => e,
None => return Err(Error::new(ENOENT)),
};
let entry = entry_res?;
let name = entry.name().map_err(|_| Error::new(EIO))?;
if name == component.as_bytes() {
break entry.inode();
}
};
}
// xopen target is link -- return EXDEV so that the file is opened as a link.
// TODO: Maybe follow initfs-local symlinks here? Would be faster
let is_link = matches!(
Self::get_inode(&self.fs, current_inode)?.kind(),
InodeKind::Link(_)
);
let o_stat_nofollow = flags & O_STAT != 0 && flags & O_NOFOLLOW != 0;
let o_symlink = flags & O_SYMLINK != 0;
if is_link && !o_stat_nofollow && !o_symlink {
return Err(Error::new(EXDEV));
}
let id = self.next_id();
let old = self.handles.insert(
id,
Handle::Node(Node {
inode: current_inode,
filename: path.into(),
}),
);
assert!(old.is_none());
Ok(OpenResult::ThisScheme {
number: id,
flags: NewFdFlags::POSITIONED,
})
}
fn read(
&mut self,
id: usize,
buffer: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
let handle = self
.handles
.get_mut(&id)
.ok_or(Error::new(EBADF))?
.as_node_mut()?;
match Self::get_inode(&self.fs, handle.inode)?.kind() {
InodeKind::File(file) => {
let data = file.data().map_err(|_| Error::new(EIO))?;
let src_buf = &data[core::cmp::min(offset, data.len())..];
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
Ok(to_copy)
}
InodeKind::Dir(_) => Err(Error::new(EISDIR)),
InodeKind::Link(link) => {
let link_data = link.data().map_err(|_| Error::new(EIO))?;
let src_buf = &link_data[core::cmp::min(offset, link_data.len())..];
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
Ok(to_copy)
}
InodeKind::Unknown => Err(Error::new(EIO)),
}
}
fn getdents<'buf>(
&mut self,
id: usize,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
let Ok(offset) = u32::try_from(opaque_offset) else {
return Ok(buf);
};
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
let InodeKind::Dir(dir) = Self::get_inode(&self.fs, handle.inode)?.kind() else {
return Err(Error::new(ENOTDIR));
};
let iter = Iter { dir, idx: offset };
for (index, entry) in iter.enumerate() {
let entry = entry?;
buf.entry(DirEntry {
// TODO: Add getter
//inode: entry.inode(),
inode: 0,
name: entry
.name()
.ok()
.and_then(|utf8| core::str::from_utf8(utf8).ok())
.ok_or(Error::new(EIO))?,
next_opaque_id: index as u64 + 1,
kind: DirentKind::Unspecified,
})?;
}
Ok(buf)
}
fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result<u64> {
let handle = self
.handles
.get_mut(&id)
.ok_or(Error::new(EBADF))?
.as_node_mut()?;
Ok(inode_len(Self::get_inode(&self.fs, handle.inode)?)? as u64)
}
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
Ok(0)
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
// TODO: Copy scheme part in kernel
let scheme_path = b"/scheme/initfs";
let scheme_bytes = core::cmp::min(scheme_path.len(), buf.len());
buf[..scheme_bytes].copy_from_slice(&scheme_path[..scheme_bytes]);
let source = handle.filename.as_bytes();
let path_bytes = core::cmp::min(buf.len() - scheme_bytes, source.len());
buf[scheme_bytes..scheme_bytes + path_bytes].copy_from_slice(&source[..path_bytes]);
Ok(scheme_bytes + path_bytes)
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
let inode = Self::get_inode(&self.fs, handle.inode)?;
stat.st_ino = inode.id();
stat.st_mode = inode.mode()
| match inode.kind() {
InodeKind::Dir(_) => MODE_DIR,
InodeKind::File(_) => MODE_FILE,
InodeKind::Link(_) => MODE_SYMLINK,
_ => 0,
};
stat.st_uid = 0;
stat.st_gid = 0;
stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX);
stat.st_ctime = 0;
stat.st_ctime_nsec = 0;
stat.st_mtime = 0;
stat.st_mtime_nsec = 0;
Ok(())
}
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
if !self.handles.contains_key(&id) {
return Err(Error::new(EBADF));
}
Ok(())
}
fn mmap_prep(
&mut self,
id: usize,
offset: u64,
size: usize,
flags: MapFlags,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
let Handle::Node(node) = handle else {
return Err(Error::new(EBADF));
};
let data = match Self::get_inode(&self.fs, node.inode)?.kind() {
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?,
InodeKind::Dir(_) => return Err(Error::new(EISDIR)),
InodeKind::Link(_) => return Err(Error::new(ELOOP)),
InodeKind::Unknown => return Err(Error::new(EIO)),
};
if flags.contains(MapFlags::PROT_WRITE) {
return Err(Error::new(EPERM));
}
let Some(last_addr) = offset.checked_add(size as u64) else {
return Err(Error::new(EINVAL));
};
if last_addr > data.len().next_multiple_of(PAGE_SIZE) as u64 {
return Err(Error::new(EINVAL));
}
Ok(data.as_ptr() as usize)
}
}
pub fn run(bytes: &'static [u8], sync_pipe: FdGuard, socket: Socket) -> ! {
log::info!("bootstrap: starting initfs scheme");
let mut state = SchemeState::new();
let mut scheme = InitFsScheme::new(bytes);
// send open-capability to bootstrap
let new_id = scheme.next_id();
scheme.handles.insert(new_id, Handle::SchemeRoot);
let cap_fd = socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("failed to issue initfs root fd");
let _ = syscall::call_rw(
sync_pipe.as_raw_fd(),
&mut cap_fd.to_ne_bytes(),
CallFlags::FD,
&[],
);
drop(sync_pipe);
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("bootstrap: failed to read scheme request from kernel")
else {
break;
};
match req.kind() {
RequestKind::Call(req) => {
let resp = req.handle_sync(&mut scheme, &mut state);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
RequestKind::OnClose { id } => {
scheme.handles.remove(&id);
}
_ => (),
}
}
unreachable!()
}
// TODO: Restructure bootstrap so it calls into relibc, or a split-off derivative without the C
// parts, such as "redox-rt".
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_read_v1(fd: usize, ptr: *mut u8, len: usize) -> isize {
Error::mux(syscall::read(fd, unsafe {
core::slice::from_raw_parts_mut(ptr, len)
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_write_v1(fd: usize, ptr: *const u8, len: usize) -> isize {
Error::mux(syscall::write(fd, unsafe {
core::slice::from_raw_parts(ptr, len)
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> isize {
Error::mux(syscall::dup(fd, unsafe {
core::slice::from_raw_parts(buf, len)
})) as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn redox_close_v1(fd: usize) -> isize {
Error::mux(syscall::close(fd)) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_sys_call_v0(
fd: usize,
payload: *mut u8,
payload_len: usize,
flags: usize,
metadata: *const u64,
metadata_len: usize,
) -> isize {
let flags = CallFlags::from_bits_retain(flags);
let metadata = unsafe { core::slice::from_raw_parts(metadata, metadata_len) };
let result = if flags.contains(CallFlags::READ) {
let payload = unsafe { core::slice::from_raw_parts_mut(payload, payload_len) };
if flags.contains(CallFlags::WRITE) {
syscall::call_rw(fd, payload, flags, metadata)
} else {
syscall::call_ro(fd, payload, flags, metadata)
}
} else {
let payload = unsafe { core::slice::from_raw_parts(payload, payload_len) };
syscall::call_wo(fd, payload, flags, metadata)
};
Error::mux(result) as isize
}
@@ -0,0 +1,560 @@
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::fmt::Debug;
use core::mem;
use hashbrown::HashMap;
use libredox::protocol::{NsDup, NsPermissions};
use log::{error, warn};
use redox_path::RedoxPath;
use redox_path::RedoxScheme;
use redox_rt::proc::FdGuard;
use redox_scheme::{
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
scheme::{SchemeState, SchemeSync},
};
use syscall::Stat;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::{CallFlags, FobtainFdFlags, error::*, schemev2::NewFdFlags};
#[derive(Debug, Clone)]
struct Namespace {
schemes: HashMap<String, Arc<FdGuard>>,
}
impl Namespace {
fn fork(&self, buf: &[u8]) -> Result<Self> {
let mut schemes = HashMap::new();
let mut cursor = 0;
while cursor < buf.len() {
let len = read_num::<usize>(&buf[cursor..])?;
cursor += mem::size_of::<usize>();
let name = String::from_utf8(Vec::from(&buf[cursor..cursor + len]))
.map_err(|_| Error::new(EINVAL))?;
cursor += len;
if name.ends_with('*') {
let prefix = &name[..name.len() - 1];
for (registered_name, fd) in &self.schemes {
if registered_name.starts_with(prefix) {
schemes.insert(registered_name.clone(), fd.clone());
}
}
} else {
let Some(fd) = self.schemes.get(&name) else {
warn!("Scheme {} not found in namespace", name);
continue;
};
schemes.insert(name, fd.clone());
}
}
Ok(Self { schemes })
}
fn get_scheme_fd(&self, scheme: &str) -> Option<&Arc<FdGuard>> {
self.schemes.get(scheme)
}
fn remove_scheme(&mut self, scheme: &str) -> Option<()> {
self.schemes.remove(scheme).map(|_| ())
}
}
#[derive(Debug, Clone)]
struct NamespaceAccess {
namespace: Rc<RefCell<Namespace>>,
permission: NsPermissions,
}
impl NamespaceAccess {
fn has_permission(&self, permission: NsPermissions) -> bool {
self.permission.contains(permission)
}
}
#[derive(Debug, Clone)]
struct SchemeRegister {
target_namespace: Rc<RefCell<Namespace>>,
scheme_name: String,
}
impl SchemeRegister {
fn register(&self, fd: FdGuard) -> Result<()> {
let mut ns = self.target_namespace.borrow_mut();
if ns.schemes.contains_key(&self.scheme_name) {
return Err(Error::new(EEXIST));
}
ns.schemes.insert(self.scheme_name.clone(), Arc::new(fd));
Ok(())
}
}
#[derive(Debug, Clone)]
enum Handle {
Access(NamespaceAccess),
Register(SchemeRegister),
List(NamespaceAccess),
}
pub struct NamespaceScheme<'sock> {
socket: &'sock Socket,
handles: HashMap<usize, Handle>,
root_namespace: Namespace,
next_id: usize,
scheme_creation_cap: FdGuard,
}
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
impl<'sock> NamespaceScheme<'sock> {
pub fn new(
socket: &'sock Socket,
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuard,
) -> Self {
Self {
socket,
handles: HashMap::new(),
root_namespace: Namespace { schemes },
next_id: 0,
scheme_creation_cap,
}
}
fn add_namespace(&mut self, id: usize, schemes: Namespace, permission: NsPermissions) {
let handle = Handle::Access(NamespaceAccess {
namespace: Rc::new(RefCell::new(schemes)),
permission,
});
self.handles.insert(id, handle);
}
fn get_ns_access(&self, id: usize) -> Option<&NamespaceAccess> {
let handle = self.handles.get(&id);
match handle {
Some(Handle::Access(access)) => Some(access),
_ => None,
}
}
fn open_namespace_resource(
&self,
ns_access: &NamespaceAccess,
reference: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
match reference {
"scheme-creation-cap" => {
if !ns_access.has_permission(NsPermissions::SCHEME_CREATE) {
error!("Permission denied to get scheme creation capability");
return Err(Error::new(EACCES));
}
Ok(syscall::dup(self.scheme_creation_cap.as_raw_fd(), &[])?)
}
_ => {
error!("Unknown special reference: {}", reference);
return Err(Error::new(EINVAL));
}
}
}
fn open_scheme_resource(
&self,
ns: &Namespace,
scheme: &str,
reference: &str,
flags: usize,
fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<usize> {
let Some(cap_fd) = ns.get_scheme_fd(scheme) else {
log::info!("Scheme {:?} not found in namespace", scheme);
return Err(Error::new(ENODEV));
};
let scheme_fd = syscall::openat_with_filter(
cap_fd.as_raw_fd(),
reference,
flags,
fcntl_flags as usize,
ctx.uid,
ctx.gid,
)?;
Ok(scheme_fd)
}
fn fork_namespace(&mut self, namespace: Rc<RefCell<Namespace>>, names: &[u8]) -> Result<usize> {
let new_id = self.next_id;
let new_namespace = namespace.borrow().fork(names).map_err(|e| {
error!("Failed to fork namespace {}: {}", new_id, e);
e
})?;
self.add_namespace(
new_id,
new_namespace,
NsPermissions::all().difference(HIGH_PERMISSIONS),
);
self.next_id += 1;
Ok(new_id)
}
fn shrink_permissions(
&mut self,
mut ns: NamespaceAccess,
permission: NsPermissions,
) -> Result<usize> {
ns.permission = ns.permission.intersection(permission);
let next_id = self.next_id;
self.handles.insert(next_id, Handle::Access(ns));
self.next_id += 1;
Ok(next_id)
}
}
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
fn openat(
&mut self,
fd: usize,
path: &str,
flags: usize,
fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
let ns_access = {
let handle = self.handles.get(&fd);
match handle {
Some(Handle::Access(access)) => Some(access),
_ => None,
}
}
.ok_or_else(|| {
error!("Namespace with ID {} not found", fd);
Error::new(ENOENT)
})?;
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
let res_fd = match scheme.as_ref() {
"namespace" => self.open_namespace_resource(
ns_access,
reference.as_ref(),
flags,
fcntl_flags,
ctx,
)?,
"" => {
if !ns_access.has_permission(NsPermissions::LIST) {
error!("Permission denied to list schemes in namespace {}", fd);
return Err(Error::new(EACCES));
}
let new_id = self.next_id;
self.next_id += 1;
self.handles.insert(new_id, Handle::List(ns_access.clone()));
return Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
});
}
_ => self.open_scheme_resource(
&ns_access.namespace.borrow(),
scheme.as_ref(),
reference.as_ref(),
flags,
fcntl_flags,
ctx,
)?,
};
Ok(OpenResult::OtherScheme { fd: res_fd })
}
fn dup(&mut self, id: usize, buf: &[u8], _ctx: &CallerCtx) -> Result<OpenResult> {
let ns_access = self.get_ns_access(id).ok_or_else(|| {
error!("Namespace with ID {} not found", id);
Error::new(ENOENT)
})?;
let raw_kind = read_num::<usize>(buf)?;
let Some(kind) = NsDup::try_from_raw(raw_kind) else {
error!("Unknown dup kind: {}", raw_kind);
return Err(Error::new(EINVAL));
};
let payload = &buf[mem::size_of::<NsDup>()..];
let new_id = match kind {
NsDup::ForkNs => {
let ns = ns_access.namespace.clone();
let _ = ns_access;
self.fork_namespace(ns, payload)?
}
NsDup::ShrinkPermissions => self.shrink_permissions(
ns_access.clone(),
NsPermissions::from_bits_truncate(read_num::<usize>(payload)?),
)?,
NsDup::IssueRegister => {
let name = core::str::from_utf8(payload).map_err(|_| Error::new(EINVAL))?;
let scheme_name = RedoxScheme::new(name).ok_or_else(|| {
error!("Invalid scheme name: {}", name);
Error::new(EINVAL)
})?;
if !ns_access.has_permission(NsPermissions::INSERT) {
error!(
"Permission denied to issue register capability for namespace {}",
id
);
return Err(Error::new(EACCES));
}
let new_id = self.next_id;
let register_cap = Handle::Register(SchemeRegister {
target_namespace: ns_access.namespace.clone(),
scheme_name: scheme_name.as_ref().to_string(),
});
self.handles.insert(new_id, register_cap);
self.next_id += 1;
new_id
}
};
Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
})
}
fn unlinkat(&mut self, fd: usize, path: &str, flags: usize, ctx: &CallerCtx) -> Result<()> {
let ns_access = self.get_ns_access(fd).ok_or_else(|| {
error!("Namespace with ID {} not found", fd);
Error::new(ENOENT)
})?;
let mut ns = ns_access.namespace.borrow_mut();
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
if reference.as_ref().is_empty() {
if !ns_access.has_permission(NsPermissions::DELETE) {
error!("Permission denied to remove scheme for namespace {}", fd);
return Err(Error::new(EACCES));
}
match ns.remove_scheme(scheme.as_ref()) {
Some(_) => return Ok(()),
None => {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
}
}
}
let Some(cap_fd) = ns.get_scheme_fd(scheme.as_ref()) else {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
};
syscall::unlinkat_with_filter(cap_fd.as_raw_fd(), reference, flags, ctx.uid, ctx.gid)?;
Ok(())
}
fn on_close(&mut self, id: usize) {
self.handles.remove(&id);
}
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let namespace_id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
let handle = self.handles.get(&namespace_id).ok_or_else(|| {
error!("Namespace with ID {} not found", namespace_id);
Error::new(ENOENT)
})?;
let Handle::Register(register_cap) = handle else {
error!(
"Handle with ID {} is not a register capability",
namespace_id
);
return Err(Error::new(EACCES));
};
if num_fds == 0 {
return Ok(0);
}
if num_fds > 1 {
error!("Can only send one fd at a time");
return Err(Error::new(EINVAL));
}
let mut new_fd = usize::MAX;
if let Err(e) = sendfd_request.obtain_fd(
&self.socket,
FobtainFdFlags::UPPER_TBL,
core::slice::from_mut(&mut new_fd),
) {
error!("on_sendfd: obtain_fd failed with error: {:?}", e);
return Err(e);
}
register_cap.register(FdGuard::new(new_fd))?;
Ok(num_fds)
}
fn getdents<'buf>(
&mut self,
id: usize,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
let Handle::List(ns_access) = self.handles.get(&id).ok_or(Error::new(EBADF))? else {
return Err(Error::new(ENOTDIR));
};
if !ns_access.has_permission(NsPermissions::LIST) {
return Err(Error::new(EACCES));
}
let ns = ns_access.namespace.borrow();
let opaque_offset = opaque_offset as usize;
for (i, (name, _)) in ns.schemes.iter().enumerate().skip(opaque_offset) {
if name.is_empty() {
continue;
}
if let Err(err) = buf.entry(DirEntry {
kind: DirentKind::Unspecified,
name: &name.clone(),
inode: 0,
next_opaque_id: i as u64 + 1,
}) {
if err.errno == EINVAL && i > opaque_offset {
// POSIX allows partial result of getdents
break;
} else {
return Err(err);
}
}
}
Ok(buf)
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
let resource_stat = match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::List(_) => Stat {
st_mode: 0o444 | syscall::MODE_DIR,
st_uid: 0,
st_gid: 0,
st_size: 0,
..Default::default()
},
Handle::Access(_) | Handle::Register(_) => Stat {
st_mode: 0o666 | syscall::MODE_FILE,
st_uid: 0,
st_gid: 0,
st_size: 0,
..Default::default()
},
};
*stat = resource_stat;
Ok(())
}
}
trait NumFromBytes: Sized + Debug {
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error>;
}
macro_rules! num_from_bytes_impl {
($($t:ty),*) => {
$(
impl NumFromBytes for $t {
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error> {
let size = mem::size_of::<Self>();
let buffer_slice = buffer.get(..size).and_then(|s| s.try_into().ok());
if let Some(slice) = buffer_slice {
Ok(Self::from_le_bytes(slice))
} else {
error!(
"read_num: buffer is too short to read num of size {} (buffer len: {})",
size, buffer.len()
);
Err(Error::new(EINVAL))
}
}
}
)*
};
}
num_from_bytes_impl!(usize);
fn read_num<T>(buffer: &[u8]) -> Result<T, Error>
where
T: NumFromBytes,
{
T::from_le_bytes_slice(buffer)
}
pub fn run(
sync_pipe: FdGuard,
socket: Socket,
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuard,
) -> ! {
let mut state = SchemeState::new();
let mut scheme = NamespaceScheme::new(&socket, schemes, scheme_creation_cap);
// send namespace fd to bootstrap
let new_id = scheme.next_id;
scheme.add_namespace(new_id, scheme.root_namespace.clone(), NsPermissions::all());
scheme.next_id += 1;
let cap_fd = scheme
.socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("nsmgr: failed to create namespace fd");
let _ = syscall::call_wo(
sync_pipe.as_raw_fd(),
&cap_fd.to_ne_bytes(),
CallFlags::FD,
&[],
);
drop(sync_pipe);
log::info!("bootstrap: namespace scheme start!");
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("bootstrap: failed to read scheme request from kernel")
else {
break;
};
match req.kind() {
RequestKind::Call(req) => {
let resp = req.handle_sync(&mut scheme, &mut state);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
RequestKind::OnClose { id } => scheme.on_close(id),
RequestKind::SendFd(sendfd_request) => {
let result = scheme.on_sendfd(&sendfd_request);
let resp = Response::new(result, sendfd_request);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
_ => (),
}
}
unreachable!()
}
+154
View File
@@ -0,0 +1,154 @@
#![no_std]
#![no_main]
#![allow(internal_features)]
#![feature(core_intrinsics, str_from_raw_parts, never_type)]
#[cfg(target_arch = "aarch64")]
#[path = "aarch64.rs"]
pub mod arch;
#[cfg(target_arch = "x86")]
#[path = "i686.rs"]
pub mod arch;
#[cfg(target_arch = "x86_64")]
#[path = "x86_64.rs"]
pub mod arch;
#[cfg(target_arch = "riscv64")]
#[path = "riscv64.rs"]
pub mod arch;
pub mod exec;
pub mod initfs;
pub mod initnsmgr;
pub mod procmgr;
pub mod start;
extern crate alloc;
use core::cell::UnsafeCell;
use alloc::collections::btree_map::BTreeMap;
use redox_rt::proc::FdGuard;
use syscall::data::Map;
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::MapFlags;
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
use core::fmt::Write;
struct Writer;
impl Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
syscall::write(1, s.as_bytes())
.map_err(|_| core::fmt::Error)
.map(|_| ())
}
}
let _ = writeln!(&mut Writer, "{}", info);
core::intrinsics::abort();
}
const HEAP_OFF: usize = arch::USERMODE_END / 2;
struct Allocator;
#[global_allocator]
static ALLOCATOR: Allocator = Allocator;
struct AllocStateInner {
heap: Option<linked_list_allocator::Heap>,
heap_top: usize,
}
struct AllocState(UnsafeCell<AllocStateInner>);
unsafe impl Send for AllocState {}
unsafe impl Sync for AllocState {}
static ALLOC_STATE: AllocState = AllocState(UnsafeCell::new(AllocStateInner {
heap: None,
heap_top: HEAP_OFF + SIZE,
}));
const SIZE: usize = 1024 * 1024;
const HEAP_INCREASE_BY: usize = SIZE;
unsafe impl alloc::alloc::GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
let state = unsafe { &mut (*ALLOC_STATE.0.get()) };
let heap = state.heap.get_or_insert_with(|| {
state.heap_top = HEAP_OFF + SIZE;
let _ = unsafe {
syscall::fmap(
!0,
&Map {
offset: 0,
size: SIZE,
address: HEAP_OFF,
flags: MapFlags::PROT_WRITE
| MapFlags::PROT_READ
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_FIXED_NOREPLACE,
},
)
}
.expect("failed to map initial heap");
unsafe { linked_list_allocator::Heap::new(HEAP_OFF as *mut u8, SIZE) }
});
match heap.allocate_first_fit(layout) {
Ok(p) => p.as_ptr(),
Err(_) => {
if layout.size() > HEAP_INCREASE_BY || layout.align() > 4096 {
return core::ptr::null_mut();
}
let _ = unsafe {
syscall::fmap(
!0,
&Map {
offset: 0,
size: HEAP_INCREASE_BY,
address: state.heap_top,
flags: MapFlags::PROT_WRITE
| MapFlags::PROT_READ
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_FIXED_NOREPLACE,
},
)
}
.expect("failed to extend heap");
unsafe { heap.extend(HEAP_INCREASE_BY) };
state.heap_top += HEAP_INCREASE_BY;
return unsafe { self.alloc(layout) };
}
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
unsafe {
(&mut *ALLOC_STATE.0.get())
.heap
.as_mut()
.unwrap()
.deallocate(core::ptr::NonNull::new(ptr).unwrap(), layout)
}
}
}
pub struct KernelSchemeMap(BTreeMap<GlobalSchemes, FdGuard>);
impl KernelSchemeMap {
fn new(kernel_scheme_infos: &[KernelSchemeInfo]) -> Self {
let mut map = BTreeMap::new();
for info in kernel_scheme_infos {
if let Some(scheme_id) = GlobalSchemes::try_from_raw(info.scheme_id) {
map.insert(scheme_id, FdGuard::new(info.fd));
}
}
Self(map)
}
fn get(&self, scheme: GlobalSchemes) -> Option<&FdGuard> {
self.0.get(&scheme)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
ENTRY(_start)
OUTPUT_FORMAT(elf64-littleriscv)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
*(.sdata*)
. = ALIGN(4096);
__data_end = .;
__bss_start = .;
*(.bss*)
*(.sbss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
@@ -0,0 +1,47 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 1 << 38; // Assuming Sv39
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
li a7, {number}
li a0, {fd}
la a1, {map} # pointer to Map struct
li a2, {map_size} # size of Map struct
ecall
# Test for success (nonzero value).
bne a0, x0, 2f
# (failure)
unimp
2:
li sp, {stack_size}
add sp, sp, a0
mv fp, x0
jal start
# `start` must never return.
unimp
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
+86
View File
@@ -0,0 +1,86 @@
use syscall::flag::MapFlags;
mod offsets {
unsafe extern "C" {
// text (R-X)
static __text_start: u8;
static __text_end: u8;
// rodata (R--)
static __rodata_start: u8;
static __rodata_end: u8;
// data+bss (RW-)
static __data_start: u8;
static __bss_end: u8;
}
pub fn text() -> (usize, usize) {
unsafe {
(
&__text_start as *const u8 as usize,
&__text_end as *const u8 as usize,
)
}
}
pub fn rodata() -> (usize, usize) {
unsafe {
(
&__rodata_start as *const u8 as usize,
&__rodata_end as *const u8 as usize,
)
}
}
pub fn data_and_bss() -> (usize, usize) {
unsafe {
(
&__data_start as *const u8 as usize,
&__bss_end as *const u8 as usize,
)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn start() -> ! {
// Remap self, from the previous RWX
let (text_start, text_end) = offsets::text();
let (rodata_start, rodata_end) = offsets::rodata();
let (data_start, data_end) = offsets::data_and_bss();
// NOTE: Assuming the debug scheme root fd is always placed at this position
let debug_fd = syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
let _ = syscall::openat(debug_fd, "", syscall::O_RDONLY, 0); // stdin
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stdout
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stderr
unsafe {
let _ = syscall::mprotect(4096, 4096, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE)
.expect("mprotect failed for initfs header page");
let _ = syscall::mprotect(
text_start,
text_end - text_start,
MapFlags::PROT_READ | MapFlags::PROT_EXEC | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .text");
let _ = syscall::mprotect(
rodata_start,
rodata_end - rodata_start,
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .rodata");
let _ = syscall::mprotect(
data_start,
data_end - data_start,
MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .data/.bss");
let _ = syscall::mprotect(
data_end,
crate::arch::STACK_START - data_end,
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for rest of memory");
}
crate::exec::main();
}
+55
View File
@@ -0,0 +1,55 @@
ENTRY(_start)
OUTPUT_FORMAT(elf64-x86-64)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
+49
View File
@@ -0,0 +1,49 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
mov rax, {number}
mov rdi, {fd}
mov rsi, offset {map} # pointer to Map struct
mov rdx, {map_size} # size of Map struct
syscall
# Test for success (nonzero value).
cmp rax, 0
jg 1f
# (failure)
ud2
1:
# Subtract 16 since all instructions seem to hate non-canonical RSP values :)
lea rsp, [rax+{stack_size}-16]
mov rbp, rsp
# Stack has the same alignment as `size`.
call start
# `start` must never return.
ud2
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);