fix: Oracle review — delete 50 stale .bak files, update Wayland doc

- git rm 50 stale .bak patch backup files (surviving across 4+ sessions)
- Update WAYLAND-IMPLEMENTATION-PLAN.md: acknowledge kded6 offscreen
  workaround is temporary until Qt6 Wayland null+8 crash is fixed.
  kded6 is a headless D-Bus daemon — Wayland adds no functionality.

This addresses Oracle verification gaps: stale doc cleanup now committed,
doc/code contradiction resolved by acknowledging the temporary nature
of the kded6 offscreen workaround.
This commit is contained in:
2026-05-06 15:29:04 +01:00
parent 806663698c
commit 499445e52c
51 changed files with 4 additions and 10752 deletions
-490
View File
@@ -1,490 +0,0 @@
# Red Bear OS Full Configuration
# Desktop/graphics ISO for bare metal and QEMU.
#
# Build: make live CONFIG_NAME=redbear-full
# QEMU: make all CONFIG_NAME=redbear-full && make qemu
#
# Extends redbear-mini with the full desktop/graphics stack:
# Wayland, Qt6, KF6, KWin, Mesa, DRM drivers, firmware, greeter.
include = ["redbear-mini.toml"]
[general]
filesystem_size = 4096
efi_partition_size = 16
[users.messagebus]
uid = 100
gid = 100
name = "messagebus"
home = "/nonexistent"
shell = "/usr/bin/zsh"
[users.root]
password = "password"
uid = 0
gid = 0
shell = "/usr/bin/zsh"
[packages]
# Runtime driver parameter control surface.
driver-params = {}
# D-Bus IPC (required by greeter/session services)
expat = {}
dbus = {}
# Firmware loading
redbear-firmware = {}
firmware-loader = {}
# NUMA topology discovery (userspace daemon)
numad = {}
# GPU/graphics stack
redox-drm = {}
mesa = {}
libdrm = {}
libwayland = "ignore"
wayland-protocols = {}
redbear-compositor = {}
# Keyboard/input
# libxkbcommon = {} # build needed
# xkeyboard-config = {} # build needed
libevdev = {}
libinput = {}
redbear-keymapd = {}
redbear-ime = {}
redbear-accessibility = {}
# Qt6 stack
qtbase = {}
qtdeclarative = {}
qtsvg = {}
qtwayland = {}
qt6-wayland-smoke = {}
qt6-sensors = {}
# KF6 Frameworks — explicit real-build surface in alphabetical order
# kirigami: blocked (QML gate — QQuickWindow/QQmlEngine headers don't exist on Redox)
kf6-kio = {}
# kde-cli-tools = {} # blocked: direct repo cook fails
kdecoration = {}
kf6-attica = {}
kf6-karchive = {}
kf6-kauth = {}
kf6-kbookmarks = {}
kf6-kcmutils = {}
kf6-kcodecs = {}
kf6-kcolorscheme = {}
kf6-kcompletion = {}
kf6-kconfig = {}
kf6-kconfigwidgets = {}
kf6-kcoreaddons = {}
kf6-kcrash = {}
kf6-kdbusaddons = {}
kf6-kdeclarative = {}
kf6-kded6 = {}
kf6-kguiaddons = {}
kf6-ki18n = {}
kf6-kiconthemes = {}
kf6-kidletime = "ignore"
kf6-kitemmodels = {}
kf6-kitemviews = {}
kf6-kjobwidgets = {}
kf6-knotifications = {}
kf6-kpackage = {}
kf6-kservice = {}
kf6-ktextwidgets = {}
kf6-kwayland = "ignore"
kf6-kwidgetsaddons = {}
kf6-kxmlgui = {}
kf6-prison = {}
kf6-solid = {}
kf6-sonnet = {}
kf6-knewstuff = {}
kf6-kwallet = {}
kglobalacceld = {}
# kwin = {} # Blocked: Qt6 Wayland plugin import error (QML gate)
# Plasma + app packages — blocked on kirigami (QML gate)
# plasma-framework = {}
# plasma-workspace = {}
# plasma-desktop = {}
redbear-authd = {}
redbear-session-launch = {}
seatd = {}
redbear-greeter = {}
amdgpu = {}
# Core Red Bear umbrella package
redbear-meta = {}
# Phase 1 runtime validation tests (POSIX: signalfd, timerfd, eventfd, shm_open, sem_open, waitid)
relibc-phase1-tests = {}
# Desktop fonts and icons
dejavu = {}
freefont = {}
hicolor-icon-theme = {}
pop-icon-theme = {}
# Suppress legacy desktop packages
orbdata = "ignore"
orbital = "ignore"
orbterm = "ignore"
orbutils = "ignore"
cosmic-edit = "ignore"
cosmic-files = "ignore"
cosmic-icons = "ignore"
cosmic-term = "ignore"
curl = "ignore"
git = "ignore"
mc = "ignore"
#curl = "ignore" # suppressed: cascade rebuild
#git = "ignore" # suppressed: cascade rebuild
#konsole = {} # WIP: recipe exists, not yet built — blocked by libiconv fetch
#kf6-pty = {} # WIP: recipe exists, not yet built
[[files]]
path = "/lib/firmware/amdgpu"
data = ""
directory = true
mode = 0o755
[[files]]
path = "/usr/lib/fonts"
data = "/usr/share/fonts"
symlink = true
[[files]]
path = "/etc/init.d/05_boot-essential.target"
data = """
[unit]
description = "Boot essential services target"
requires_weak = [
"00_base.target",
]
"""
[[files]]
path = "/etc/init.d/13_iommu.service"
data = """
[unit]
description = "IOMMU DMA remapping daemon"
requires_weak = [
"12_boot-late.target",
"00_pcid-spawner.service",
]
[service]
cmd = "/usr/bin/iommu"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/12_dbus.service"
data = """
[unit]
description = "D-Bus system bus"
requires_weak = [
"12_boot-late.target",
"00_ipcd.service",
]
[service]
cmd = "/usr/bin/dbus-daemon"
args = ["--system", "--nopidfile"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/13_redbear-sessiond.service"
data = """
[unit]
description = "Red Bear session broker (org.freedesktop.login1)"
requires_weak = [
"12_dbus.service",
]
[service]
cmd = "/usr/bin/redbear-sessiond"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/13_seatd.service"
data = """
[unit]
description = "seatd seat management daemon"
requires_weak = [
"12_dbus.service",
"13_redbear-sessiond.service",
]
[service]
cmd = "/usr/bin/seatd"
args = ["-l", "info"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/keymaps/.gitkeep"
data = ""
[[files]]
path = "/etc/init.d/13_redbear-keymapd.service"
data = """
[unit]
description = "Runtime keymap daemon"
requires_weak = [
"10_evdevd.service",
]
[service]
cmd = "/usr/bin/redbear-keymapd"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/13_redbear-ime.service"
data = """
[unit]
description = "Input method engine daemon"
requires_weak = [
"10_evdevd.service",
]
[service]
cmd = "redbear-ime"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/13_redbear-accessibility.service"
data = """
[unit]
description = "Accessibility input filter daemon (sticky/slow/bounce keys)"
requires_weak = [
"10_evdevd.service",
]
[service]
cmd = "redbear-accessibility"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/14_redbear-upower.service"
data = """
[unit]
description = "UPower D-Bus service (org.freedesktop.UPower)"
requires_weak = [
"12_dbus.service",
]
[service]
cmd = "redbear-upower"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/14_redbear-udisks.service"
data = """
[unit]
description = "UDisks2 D-Bus service (org.freedesktop.UDisks2)"
requires_weak = [
"12_dbus.service",
]
[service]
cmd = "redbear-udisks"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/14_redbear-polkit.service"
data = """
[unit]
description = "PolicyKit1 D-Bus service (org.freedesktop.PolicyKit1)"
requires_weak = [
"12_dbus.service",
]
[service]
cmd = "redbear-polkit"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/19_redbear-authd.service"
data = """
[unit]
description = "Red Bear authentication daemon"
requires_weak = [
"12_dbus.service",
]
[service]
cmd = "/usr/bin/redbear-authd"
envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb", KWIN_DRM_DEVICES = "/scheme/drm/card0" }
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/20_display.service"
data = """
[unit]
description = "KDE session assembly helper"
requires_weak = [
"12_dbus.service",
"13_redbear-sessiond.service",
"13_seatd.service",
"13_redbear-keymapd.service",
"19_redbear-authd.service",
]
[service]
cmd = "/usr/bin/redbear-session-launch"
args = ["--username", "root", "--mode", "session", "--session", "kde-wayland", "--vt", "4", "--runtime-dir", "/tmp/run/redbear-display-session", "--wayland-display", "wayland-display"]
envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb", REDBEAR_KDE_SESSION_BACKEND = "virtual", REDBEAR_KDE_SESSION_STATE_DIR = "/run/redbear-display-session" }
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/20_greeter.service"
data = """
[unit]
description = "Red Bear greeter service"
requires_weak = [
"00_pcid-spawner.service",
"12_dbus.service",
"13_redbear-sessiond.service",
"13_seatd.service",
"13_redbear-keymapd.service",
"19_redbear-authd.service",
]
[service]
cmd = "/usr/bin/redbear-greeterd"
envs = { VT = "3", REDBEAR_GREETER_USER = "greeter", KWIN_DRM_DEVICES = "/scheme/drm/card0", REDBEAR_DRM_WAIT_SECONDS = "10" }
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/29_activate_console.service"
data = """
[unit]
description = "Activate fallback console VT"
requires_weak = [
"05_boot-essential.target",
]
[service]
cmd = "inputd"
args = ["-A", "2"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/30_console.service"
data = """
[unit]
description = "Console terminals"
requires_weak = [
"29_activate_console.service",
]
[service]
cmd = "getty"
args = ["2"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/31_debug_console.service"
data = """
[unit]
description = "Debug console on serial port"
requires_weak = [
"29_activate_console.service",
]
[service]
cmd = "getty"
args = ["/scheme/debug/no-preserve", "-J"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/99_diag_serial.service"
data = """
[unit]
description = "Serial diagnostic marker"
requires_weak = [
"31_debug_console.service",
"30_console.service",
"12_dbus.service",
]
[service]
cmd = "ion"
args = ["-c", "echo BOOT_COMPLETE_SERIAL_MARKER"]
type = "oneshot"
"""
[users.greeter]
password = ""
uid = 101
gid = 101
name = "greeter"
home = "/nonexistent"
shell = "/usr/bin/zsh"
[groups.greeter]
gid = 101
members = ["greeter"]
[groups.messagebus]
gid = 100
members = ["messagebus"]
[[files]]
path = "/etc/pcid.d/ihdgd.toml"
data = """
[[drivers]]
name = "Intel GPU (VGA compatible)"
class = 0x03
vendor = 0x8086
subclass = 0x00
command = ["redox-drm"]
[[drivers]]
name = "Intel GPU (3D controller)"
class = 0x03
vendor = 0x8086
subclass = 0x02
command = ["redox-drm"]
"""
[[files]]
path = "/etc/pcid.d/virtio-gpud.toml"
data = """
[[drivers]]
name = "VirtIO GPU"
class = 0x03
vendor = 0x1af4
subclass = 0x00
command = ["redox-drm"]
"""
[[files]]
path = "/etc/environment.d/90-dbus.conf"
data = """
DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket
"""
+4 -3
View File
@@ -20,9 +20,10 @@ Wayland wrappers or in the relibc/libwayland client stack.
inevitable: Qt6 Wayland must work on Redox.
**Removed workarounds:**
- ~~`QT_QPA_PLATFORM=offscreen` for kded6~~
- ~~`QT_QPA_PLATFORM=redox` in greeter-ui main.cpp~~
- kded6 must use Wayland. greeter-ui must use Wayland.
- ~~`QT_QPA_PLATFORM=redox` in greeter-ui main.cpp~~ — removed; greeter must use Wayland
- kded6 temporarily uses `offscreen` via `#ifdef Q_OS_REDOX` and D-Bus service `env` until
the Qt6 Wayland null+8 crash is fixed. kded6 is a headless D-Bus daemon — using Wayland
adds no functionality.
## Current Blocker: Qt6 Wayland Client Crash
@@ -1,55 +0,0 @@
From: Red Bear OS
Date: 2026-04-28
Subject: daemon: handle missing INIT_NOTIFY gracefully instead of panicking
The Daemon::new() and Daemon::ready() functions in the daemon library
called unwrap() on the INIT_NOTIFY environment variable and the ready
pipe write, causing a hard panic when a daemon is started outside the
init system's notification pipe mechanism.
Replace unwrap() with graceful error handling:
- get_fd() returns -1 if the env var is missing or invalid, logging
a warning via eprintln
- ready() logs a warning on write failure instead of panicking
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
index 9f507221..a0ba9d88 100644
--- a/daemon/src/lib.rs
+++ b/daemon/src/lib.rs
@@ -11,12 +11,23 @@ use redox_scheme::Socket;
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
unsafe fn get_fd(var: &str) -> RawFd {
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
+ let fd: RawFd = match std::env::var(var)
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
+ .ok()
+ .and_then(|val| {
+ val.parse()
+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
+ .ok()
+ }) {
+ Some(fd) => fd,
+ None => return -1,
+ };
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
- panic!(
+ eprintln!(
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
io::Error::last_os_error()
);
+ return -1;
}
fd
}
@@ -50,8 +61,10 @@ impl Daemon {
/// Notify the process that the daemon is ready to accept requests.
pub fn ready(mut self) {
- self.write_pipe.write_all(&[0]).unwrap();
+ if let Err(err) = self.write_pipe.write_all(&[0]) {
+ eprintln!("daemon::ready write failed: {err}");
+ }
}
/// Executes `Command` as a child process.
@@ -1,61 +0,0 @@
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
index ce55b33f..c06bdec4 100644
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -21,6 +21,10 @@ enum Handle {
Access,
Device,
Channel { addr: PciAddress, st: ChannelState },
+ // Uevent surface for hotplug consumers. Opening uevent returns an object
+ // from which device add/remove events can be read. Since pcid currently
+ // only scans at startup, this surface is ready for hotplug polling consumers.
+ Uevent,
SchemeRoot,
/// Represents an open handle to a device's bind endpoint
Bind { addr: PciAddress },
@@ -34,7 +38,7 @@ struct HandleWrapper {
}
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. })
+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
}
fn is_dir(&self) -> bool {
!self.is_file()
@@ -96,6 +100,8 @@ impl SchemeSync for PciScheme {
}
} else if path == "access" {
Handle::Access
+ } else if path == "uevent" {
+ Handle::Uevent
} else {
let idx = path.find('/').unwrap_or(path.len());
let (addr_str, after) = path.split_at(idx);
@@ -140,6 +146,7 @@ impl SchemeSync for PciScheme {
Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
+ Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
stat.st_size = len as u64;
@@ -164,6 +171,12 @@ impl SchemeSync for PciScheme {
Handle::Channel {
addr: _,
ref mut st,
} => Self::read_channel(st, buf),
+ Handle::Uevent => {
+ // Uevent surface is ready for hotplug polling consumers.
+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available).
+ // Consumers can poll and re-read to check for new events.
+ Ok(0)
+ }
Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)),
}
@@ -199,7 +212,7 @@ impl SchemeSync for PciScheme {
}
Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => return Err(Error::new(ENOTDIR)),
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
for (i, dent_name) in entries.iter().enumerate().skip(offset) {
@@ -1,20 +0,0 @@
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index f1c6d08e..a3f2e15c 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -904,6 +904,7 @@ impl<const N: usize> Xhci<N> {
match self.spawn_drivers(port_id) {
Ok(()) => {
info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num());
+ // NOTE: driver-manager hotplug loop detects new USB devices via this log
}
Err(err) => {
error!("Failed to spawn driver for port {}: `{}`", port_id, err)
@@ -974,6 +975,8 @@ impl<const N: usize> Xhci<N> {
info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num());
result
} else {
+ // NOTE: driver-manager hotplug loop detects USB device removal via this log
debug!(
"Attempted to detach from port {}, which wasn't previously attached.",
port_id
@@ -1,287 +0,0 @@
# P2-ac97d-ihdad-main.patch
#
# Audio daemon main entry points: AC97 and Intel HDA driver initialization,
# error handling, and BAR access improvements.
#
# Covers:
# - ac97d/src/main.rs: BAR access, error handling, codec initialization
# - ihdad/src/main.rs: error handling, device initialization
#
diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs
index ffa8a94b..e4dbf930 100644
--- a/drivers/audio/ac97d/src/main.rs
+++ b/drivers/audio/ac97d/src/main.rs
@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd;
use std::usize;
use event::{user_data, EventQueue};
+use log::error;
use pcid_interface::PciFunctionHandle;
use redox_scheme::scheme::register_sync_scheme;
use redox_scheme::Socket;
@@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
let mut name = pci_config.func.name();
name.push_str("_ac97");
- let bar0 = pci_config.func.bars[0].expect_port();
- let bar1 = pci_config.func.bars[1].expect_port();
+ let bar0 = match pci_config.func.bars[0].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
+ let bar1 = match pci_config.func.bars[1].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR1: {err}");
+ std::process::exit(1);
+ }
+ };
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("ac97d: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ error!("ac97d: no legacy interrupts supported");
+ std::process::exit(1);
+ });
println!(" + ac97 {}", pci_config.func.display());
@@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
common::file_level(),
);
- common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3");
+ if let Err(err) = common::acquire_port_io_rights() {
+ error!("ac97d: failed to set I/O privilege level to Ring 3: {err}");
+ std::process::exit(1);
+ }
- let mut irq_file = irq.irq_handle("ac97d");
+ let mut irq_file = match irq.try_irq_handle("ac97d") {
+ Ok(file) => file,
+ Err(err) => {
+ error!("ac97d: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let socket = Socket::nonblock().expect("ac97d: failed to create socket");
- let mut device =
- unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ error!("ac97d: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
+ let mut device = unsafe {
+ match device::Ac97::new(bar0, bar1) {
+ Ok(device) => device,
+ Err(err) => {
+ error!("ac97d: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
+ };
let mut readiness_based = ReadinessBased::new(&socket, 16);
user_data! {
@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("ac97d: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ error!("ac97d: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(
socket.inner().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- register_sync_scheme(&socket, "audiohw", &mut device)
- .expect("ac97d: failed to register audiohw scheme to namespace");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe scheme fd: {err}");
+ std::process::exit(1);
+ });
+
+ register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| {
+ error!("ac97d: failed to register audiohw scheme to namespace: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ error!("ac97d: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
let all = [Source::Irq, Source::Scheme];
- for event in all
- .into_iter()
- .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
- {
+ for event in all.into_iter().chain(event_queue.map(|e| match e {
+ Ok(event) => event.user_data,
+ Err(err) => {
+ error!("ac97d: failed to get next event: {err}");
+ std::process::exit(1);
+ }
+ })) {
match event {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ error!("ac97d: failed to read IRQ file: {err}");
+ std::process::exit(1);
+ }
if !device.irq() {
continue;
}
- irq_file.write(&mut irq).unwrap();
+ if let Err(err) = irq_file.write(&mut irq) {
+ error!("ac97d: failed to acknowledge IRQ: {err}");
+ std::process::exit(1);
+ }
readiness_based
.poll_all_requests(&mut device)
- .expect("ac97d: failed to poll requests");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to poll requests: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device_irq.next_read();
@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
Source::Scheme => {
readiness_based
.read_and_process_requests(&mut device)
- .expect("ac97d: failed to read from socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to read from socket: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device.borrow().next_read();
@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
index 31a2add7..11d80133 100755
--- a/drivers/audio/ihdad/src/main.rs
+++ b/drivers/audio/ihdad/src/main.rs
@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd;
use std::usize;
use event::{user_data, EventQueue};
-use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod hda;
@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("IHDA {}", pci_config.func.display());
+ if let Err(err) = pci_config.func.bars[0].try_mem() {
+ log::error!("ihdad: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
{
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
- let socket = Socket::nonblock().expect("ihdad: failed to create socket");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("ihdad: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("ihdad: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
File diff suppressed because it is too large Load Diff
@@ -1,313 +0,0 @@
diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
--- a/drivers/hwd/src/backend/acpi.rs
+++ b/drivers/hwd/src/backend/acpi.rs
@@ -1,27 +1,36 @@
use amlserde::{AmlSerde, AmlSerdeValue};
-use std::{error::Error, fs, process::Command};
+use std::{error::Error, fs};
use super::Backend;
pub struct AcpiBackend {
- rxsdt: Vec<u8>,
+ _rxsdt: Vec<u8>,
}
impl Backend for AcpiBackend {
fn new() -> Result<Self, Box<dyn Error>> {
let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?;
- // Spawn acpid
- //TODO: pass rxsdt data to acpid?
- #[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(Command::new("acpid"));
-
- Ok(Self { rxsdt })
+ Ok(Self { _rxsdt: rxsdt })
}
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
+ let mut boot_critical_input_candidates = 0usize;
+ let mut thc_candidates = 0usize;
+ let mut non_hid_i2c_candidates = 0usize;
+
// Read symbols from acpi scheme
- let entries = fs::read_dir("/scheme/acpi/symbols")?;
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
+ Ok(entries) => entries,
+ Err(err)
+ if err.kind() == std::io::ErrorKind::WouldBlock
+ || err.raw_os_error() == Some(11) =>
+ {
+ log::debug!("hwd: ACPI symbols are not ready yet");
+ return Ok(());
+ }
+ Err(err) => return Err(Box::new(err)),
+ };
// TODO: Reimplement with getdents?
let symbols_fd = libredox::Fd::open(
"/scheme/acpi/symbols",
@@ -100,12 +109,103 @@
"PNP0C0F" => "PCI interrupt link",
"PNP0C50" => "I2C HID",
"PNP0F13" => "PS/2 port for PS/2-style mouse",
+ "80860F41" | "808622C1" => "DesignWare I2C controller",
+ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller",
+ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => {
+ "Intel LPSS/SerialIO I2C controller"
+ }
+ "INT34C5" | "INTC1055" => "Intel GPIO controller",
+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => {
+ "Intel THC companion (QuickI2C/QuickSPI path)"
+ }
+ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)",
+ _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)",
+ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)",
_ => "?",
};
log::debug!("{}: {} ({})", name, id, what);
+ if is_boot_critical_i2c_surface(&id) {
+ boot_critical_input_candidates += 1;
+ log::info!("{}: {} is boot-critical for laptop input path", name, id);
+ }
+ if is_thc_companion(&id) {
+ thc_candidates += 1;
+ log::warn!(
+ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet",
+ name,
+ id
+ );
+ }
+ if is_non_hid_i2c_input_id(&id) {
+ non_hid_i2c_candidates += 1;
+ }
}
}
}
+
+ if boot_critical_input_candidates == 0 {
+ log::warn!(
+ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support"
+ );
+ } else {
+ log::info!(
+ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}",
+ boot_critical_input_candidates,
+ thc_candidates,
+ non_hid_i2c_candidates
+ );
+ }
+
Ok(())
}
}
+
+fn is_boot_critical_i2c_surface(id: &str) -> bool {
+ matches!(
+ id,
+ "PNP0C50"
+ | "ACPI0C50"
+ | "80860F41"
+ | "808622C1"
+ | "AMDI0010"
+ | "AMDI0019"
+ | "AMDI0510"
+ | "INT33C2"
+ | "INT33C3"
+ | "INT3432"
+ | "INT3433"
+ | "INTC10EF"
+ | "INT34C5"
+ | "INTC1055"
+ | "INTC1050"
+ | "INTC1051"
+ | "INTC1080"
+ | "INTC1081"
+ | "INTC1082"
+ ) || is_elan_touchpad_id(id)
+ || is_cypress_touchpad_id(id)
+ || is_synaptics_rmi_id(id)
+}
+
+fn is_thc_companion(id: &str) -> bool {
+ matches!(
+ id,
+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082"
+ )
+}
+
+fn is_elan_touchpad_id(id: &str) -> bool {
+ id.starts_with("ELAN")
+}
+
+fn is_cypress_touchpad_id(id: &str) -> bool {
+ id.starts_with("CYAP")
+}
+
+fn is_synaptics_rmi_id(id: &str) -> bool {
+ id.starts_with("SYNA")
+}
+
+fn is_non_hid_i2c_input_id(id: &str) -> bool {
+ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id)
+}
diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs
--- a/drivers/pcid-spawner/src/main.rs
+++ b/drivers/pcid-spawner/src/main.rs
@@ -1,11 +1,40 @@
+use std::env;
use std::fs;
use std::process::Command;
+use std::thread;
use anyhow::{anyhow, Context, Result};
use pcid_interface::config::Config;
use pcid_interface::PciFunctionHandle;
+fn strict_usb_boot() -> bool {
+ matches!(
+ env::var("REDBEAR_STRICT_USB_BOOT")
+ .ok()
+ .as_deref()
+ .map(str::to_ascii_lowercase)
+ .as_deref(),
+ Some("1" | "true" | "yes" | "on")
+ )
+}
+
+fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool {
+ if !initfs {
+ return false;
+ }
+
+ if class == 0x01 {
+ return false;
+ }
+
+ if strict_usb_boot && class == 0x0C && subclass == 0x03 {
+ return false;
+ }
+
+ true
+}
+
fn main() -> Result<()> {
let mut args = pico_args::Arguments::from_env();
let initfs = args.contains("--initfs");
@@ -30,6 +59,7 @@
}
let config: Config = toml::from_str(&config_data)?;
+ let strict_usb_boot = strict_usb_boot();
for entry in fs::read_dir("/scheme/pci")? {
let entry = entry.context("failed to get entry")?;
@@ -87,14 +117,70 @@
log::info!("pcid-spawner: spawn {:?}", command);
+ let device_addr = handle.config().func.addr;
+
handle.enable_device();
let channel_fd = handle.into_inner_fd();
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
#[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(command);
- syscall::close(channel_fd as usize).unwrap();
+ if should_detach_in_initfs(
+ initfs,
+ full_device_id.class,
+ full_device_id.subclass,
+ strict_usb_boot,
+ ) {
+ log::warn!(
+ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot",
+ device_addr
+ );
+
+ let device_addr = device_addr.to_string();
+ thread::spawn(move || {
+ #[allow(deprecated, reason = "we can't yet move this to init")]
+ if let Err(err) = daemon::Daemon::spawn(command) {
+ log::error!(
+ "pcid-spawner: spawn/readiness failed for {}: {}",
+ device_addr,
+ err
+ );
+ log::error!(
+ "pcid-spawner: {} remains enabled without a confirmed ready driver",
+ device_addr
+ );
+ }
+ if let Err(err) = syscall::close(channel_fd as usize) {
+ log::error!(
+ "pcid-spawner: failed to close channel fd {} for {}: {}",
+ channel_fd,
+ device_addr,
+ err
+ );
+ }
+ });
+ } else {
+ #[allow(deprecated, reason = "we can't yet move this to init")]
+ if let Err(err) = daemon::Daemon::spawn(command) {
+ log::error!(
+ "pcid-spawner: spawn/readiness failed for {}: {}",
+ device_addr,
+ err
+ );
+ log::error!(
+ "pcid-spawner: {} remains enabled without a confirmed ready driver",
+ device_addr
+ );
+ }
+ if let Err(err) = syscall::close(channel_fd as usize) {
+ log::error!(
+ "pcid-spawner: failed to close channel fd {} for {}: {}",
+ channel_fd,
+ device_addr,
+ err
+ );
+ }
+ }
}
Ok(())
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -12,6 +12,7 @@
};
use redox_scheme::scheme::register_sync_scheme;
use scheme_utils::Blocking;
+use syscall::{sendfd, SendFdFlags};
use crate::cfg_access::Pcie;
use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom};
@@ -262,14 +263,13 @@
let access_fd = socket
.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0)
.expect("failed to issue this resource");
- let access_bytes = access_fd.to_ne_bytes();
- let _ = register_pci
- .call_wo(
- &access_bytes,
- syscall::CallFlags::WRITE | syscall::CallFlags::FD,
- &[],
- )
- .expect("failed to send pci_fd to acpid");
+ sendfd(
+ register_pci.raw(),
+ access_fd as usize,
+ SendFdFlags::empty().bits(),
+ 0,
+ )
+ .expect("failed to send pci_fd to acpid");
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -1,144 +0,0 @@
# P2-boot-runtime-noise-and-net-race.patch
#
# Reduce expected boot-time warning noise and harden netstack startup ordering:
# - procmgr: unknown cancellation is trace-level (benign race)
# - acpid: warn once for unsupported power surface
# - ahcid: SATAPI probe failures are informational on empty media
# - netstack: retry network adapter discovery during early boot races
diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs
--- a/bootstrap/src/procmgr.rs
+++ b/bootstrap/src/procmgr.rs
@@ -296,7 +296,7 @@ fn handle_scheme<'a>(
}
}
} else {
- log::warn!("Cancellation for unknown id {:?}", req.id);
+ log::trace!("Cancellation for unknown id {:?}", req.id);
Pending
}
}
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -8,6 +8,7 @@ use ron::de::SpannedError;
use scheme_utils::HandleMap;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
+use std::sync::atomic::{AtomicBool, Ordering};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::schemev2::NewFdFlags;
use syscall::FobtainFdFlags;
@@ -29,6 +30,8 @@ use crate::acpi::{
};
use crate::resources::{decode_resource_template, ResourceDescriptor};
+static POWER_SURFACE_UNAVAILABLE_WARNED: AtomicBool = AtomicBool::new(false);
+
pub struct AcpiScheme<'acpi, 'sock> {
ctx: &'acpi AcpiContext,
handles: HandleMap<Handle<'acpi>>,
@@ -307,7 +310,9 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
self.ctx.power_snapshot().map_err(|error| match error {
crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN),
crate::acpi::AmlEvalError::Unsupported(message) => {
- log::warn!("ACPI power surface unavailable: {message}");
+ if !POWER_SURFACE_UNAVAILABLE_WARNED.swap(true, Ordering::Relaxed) {
+ log::warn!("ACPI power surface unavailable: {message}");
+ }
Error::new(EOPNOTSUPP)
}
other => {
diff --git a/drivers/storage/ahcid/src/ahci/mod.rs b/drivers/storage/ahcid/src/ahci/mod.rs
--- a/drivers/storage/ahcid/src/ahci/mod.rs
+++ b/drivers/storage/ahcid/src/ahci/mod.rs
@@ -64,7 +64,7 @@ pub fn disks(base: usize, name: &str) -> (&'static mut HbaMem, Vec<AnyDisk>) {
HbaPortType::SATAPI => match DiskATAPI::new(i, port) {
Ok(disk) => Some(AnyDisk::Atapi(disk)),
Err(err) => {
- error!("{}: {}", i, err);
+ info!("{}: {}", i, err);
None
}
},
diff --git a/netstack/src/main.rs b/netstack/src/main.rs
--- a/netstack/src/main.rs
+++ b/netstack/src/main.rs
@@ -6,6 +6,8 @@ use anyhow::{anyhow, bail, Context, Result};
use event::{EventFlags, EventQueue};
use libredox::flag::{O_NONBLOCK, O_RDWR};
use libredox::Fd;
+use std::thread;
+use std::time::Duration;
use redox_scheme::Socket;
use scheme::Smolnetd;
@@ -22,34 +24,47 @@ mod scheme;
fn get_network_adapter() -> Result<String> {
use std::fs;
- let mut adapters = vec![];
+ const MAX_ATTEMPTS: u32 = 50;
+ const RETRY_DELAY: Duration = Duration::from_millis(100);
- for entry_res in fs::read_dir("/scheme")? {
- let Ok(entry) = entry_res else {
- continue;
- };
+ for attempt in 1..=MAX_ATTEMPTS {
+ let mut adapters = vec![];
- let Ok(scheme) = entry.file_name().into_string() else {
- continue;
- };
+ for entry_res in fs::read_dir("/scheme")? {
+ let Ok(entry) = entry_res else {
+ continue;
+ };
- if !scheme.starts_with("network") {
- continue;
- }
+ let Ok(scheme) = entry.file_name().into_string() else {
+ continue;
+ };
- adapters.push(scheme);
- }
+ if !scheme.starts_with("network") {
+ continue;
+ }
- if adapters.is_empty() {
- bail!("no network adapter found");
- } else {
- let adapter = adapters.remove(0);
+ adapters.push(scheme);
+ }
+
if !adapters.is_empty() {
- // FIXME allow using multiple network adapters at the same time
- warn!("Multiple network adapters found. Only {adapter} will be used");
+ let adapter = adapters.remove(0);
+ if !adapters.is_empty() {
+ // FIXME allow using multiple network adapters at the same time
+ warn!("Multiple network adapters found. Only {adapter} will be used");
+ }
+ return Ok(adapter);
+ }
+
+ if attempt < MAX_ATTEMPTS {
+ warn!(
+ "no network adapter found yet (attempt {attempt}/{MAX_ATTEMPTS}), waiting {} ms",
+ RETRY_DELAY.as_millis()
+ );
+ thread::sleep(RETRY_DELAY);
}
- Ok(adapter)
}
+
+ bail!("no network adapter found")
}
-18
View File
@@ -1,18 +0,0 @@
# P2-hwd-misc.patch
# Keep hwd focused on hardware probing. Init owns boot-time pcid startup.
diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs
index 79360e34..4de3d9f3 100644
--- a/drivers/hwd/src/main.rs
+++ b/drivers/hwd/src/main.rs
@@ -37,11 +37,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
//TODO: launch pcid based on backend information?
// Must launch after acpid but before probe calls /scheme/acpi/symbols
- #[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(process::Command::new("pcid"));
-
daemon.ready();
//TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers
@@ -1,33 +0,0 @@
diff --git a/init.initfs.d/41_acpid.service b/init.initfs.d/41_acpid.service
new file mode 100644
--- /dev/null
+++ b/init.initfs.d/41_acpid.service
@@ -0,0 +1,7 @@
+[unit]
+description = "ACPI daemon"
+default_dependencies = false
+
+[service]
+cmd = "acpid"
+inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
+type = "notify"
diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target
--- a/init.initfs.d/40_drivers.target
+++ b/init.initfs.d/40_drivers.target
@@ -7,4 +7,5 @@ requires_weak = [
"40_bcm2835-sdhcid.service",
"40_hwd.service",
"40_pcid-spawner-initfs.service",
+ "41_acpid.service",
]
diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service
--- a/init.initfs.d/40_hwd.service
+++ b/init.initfs.d/40_hwd.service
@@ -1,6 +1,6 @@
[unit]
description = "Hardware manager"
-requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"]
+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"]
[service]
cmd = "hwd"
@@ -1,607 +0,0 @@
# P2-network-driver-mains.patch
# Extract network driver main.rs hardening: replace panic/unwrap/expect with
# proper error handling and graceful exits.
#
# Files: drivers/net/e1000d/src/main.rs, drivers/net/ixgbed/src/main.rs,
# drivers/net/rtl8139d/src/main.rs, drivers/net/rtl8168d/src/main.rs,
# drivers/net/virtio-netd/src/main.rs
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
index 373ea9b3..8ff57b33 100644
--- a/drivers/net/e1000d/src/main.rs
+++ b/drivers/net/e1000d/src/main.rs
@@ -1,5 +1,6 @@
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
+use std::process;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
@@ -25,10 +26,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
common::file_level(),
);
- let irq = pci_config
- .func
- .legacy_interrupt_line
- .expect("e1000d: no legacy interrupts supported");
+ let irq = match pci_config.func.legacy_interrupt_line {
+ Some(irq) => irq,
+ None => {
+ log::error!("e1000d: no legacy interrupts supported");
+ process::exit(1);
+ }
+ };
log::info!("E1000 {}", pci_config.func.display());
@@ -38,7 +42,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Intel8254x::new(address).expect("e1000d: failed to allocate device")
+ device::Intel8254x::new(address).unwrap_or_else(|err| {
+ log::error!("e1000d: failed to allocate device: {err}");
+ process::exit(1);
+ })
},
daemon,
format!("network.{name}"),
@@ -51,7 +58,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("e1000d: failed to create event queue");
+ let mut event_queue = EventQueue::<Source>::new().unwrap_or_else(|err| {
+ log::error!("e1000d: failed to create event queue: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
@@ -59,32 +69,65 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
Source::Irq,
event::EventFlags::READ,
)
- .expect("e1000d: failed to subscribe to IRQ fd");
+ .unwrap_or_else(|err| {
+ log::error!("e1000d: failed to subscribe to IRQ fd: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .expect("e1000d: failed to subscribe to scheme fd");
-
- libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace");
-
- scheme.tick().unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("e1000d: failed to subscribe to scheme fd: {err}");
+ process::exit(1);
+ });
+
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ log::error!("e1000d: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
+
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed initial scheme tick: {err}");
+ process::exit(1);
+ }
- for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) {
+ loop {
+ let event = match event_queue.next() {
+ Some(Ok(event)) => event,
+ Some(Err(err)) => {
+ log::error!("e1000d: failed to get event: {err}");
+ continue;
+ }
+ None => break,
+ };
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ log::error!("e1000d: failed to read IRQ: {err}");
+ continue;
+ }
if unsafe { scheme.adapter().irq() } {
- irq_file.write(&mut irq).unwrap();
-
- scheme.tick().expect("e1000d: failed to handle IRQ")
+ if let Err(err) = irq_file.write(&mut irq) {
+ log::error!("e1000d: failed to write IRQ: {err}");
+ continue;
+ }
+
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed to handle IRQ: {err}");
+ }
+ }
+ }
+ Source::Scheme => {
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed to handle scheme op: {err}");
}
}
- Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"),
}
}
- unreachable!()
+
+ process::exit(0);
}
diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs
index 4a6ce74d..855d339d 100644
--- a/drivers/net/ixgbed/src/main.rs
+++ b/drivers/net/ixgbed/src/main.rs
@@ -1,5 +1,6 @@
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
+use std::process;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
@@ -19,12 +20,23 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut name = pci_config.func.name();
name.push_str("_ixgbe");
- let irq = pci_config
- .func
- .legacy_interrupt_line
- .expect("ixgbed: no legacy interrupts supported");
+ common::setup_logging(
+ "net",
+ "pci",
+ &name,
+ common::output_level(),
+ common::file_level(),
+ );
+
+ let irq = match pci_config.func.legacy_interrupt_line {
+ Some(irq) => irq,
+ None => {
+ log::error!("ixgbed: no legacy interrupts supported");
+ process::exit(1);
+ }
+ };
- println!(" + IXGBE {}", pci_config.func.display());
+ log::info!("IXGBE {}", pci_config.func.display());
let mut irq_file = irq.irq_handle("ixgbed");
@@ -34,8 +46,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut scheme = NetworkScheme::new(
move || {
- device::Intel8259x::new(address as usize, size)
- .expect("ixgbed: failed to allocate device")
+ device::Intel8259x::new(address as usize, size).unwrap_or_else(|err| {
+ log::error!("ixgbed: failed to allocate device: {err}");
+ process::exit(1);
+ })
},
daemon,
format!("network.{name}"),
@@ -48,41 +62,77 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("ixgbed: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::new().unwrap_or_else(|err| {
+ log::error!("ixgbed: failed to create event queue: {err}");
+ process::exit(1);
+ });
+
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("ixgbed: failed to subscribe to IRQ fd: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- libredox::call::setrens(0, 0).expect("ixgbed: failed to enter null namespace");
+ .unwrap_or_else(|err| {
+ log::error!("ixgbed: failed to subscribe to scheme fd: {err}");
+ process::exit(1);
+ });
+
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ log::error!("ixgbed: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
+
+ if let Err(err) = scheme.tick() {
+ log::error!("ixgbed: failed initial scheme tick: {err}");
+ process::exit(1);
+ }
- scheme.tick().unwrap();
+ loop {
+ let event = match event_queue.next() {
+ Some(Ok(event)) => event,
+ Some(Err(err)) => {
+ log::error!("ixgbed: failed to get event: {err}");
+ continue;
+ }
+ None => break,
+ };
- for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) {
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ log::error!("ixgbed: failed to read IRQ: {err}");
+ continue;
+ }
if scheme.adapter().irq() {
- irq_file.write(&mut irq).unwrap();
-
- scheme.tick().unwrap();
+ if let Err(err) = irq_file.write(&mut irq) {
+ log::error!("ixgbed: failed to write IRQ: {err}");
+ continue;
+ }
+
+ if let Err(err) = scheme.tick() {
+ log::error!("ixgbed: failed to handle IRQ: {err}");
+ }
}
}
Source::Scheme => {
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ log::error!("ixgbed: failed to handle scheme op: {err}");
+ }
}
}
}
- unreachable!()
+
+ process::exit(0);
}
diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs
index d470e814..64335a23 100644
--- a/drivers/net/rtl8139d/src/main.rs
+++ b/drivers/net/rtl8139d/src/main.rs
@@ -1,5 +1,6 @@
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
+use std::process;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 {
other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other),
}
}
- panic!("rtl8139d: failed to find BAR");
+ log::error!("rtl8139d: failed to find BAR");
+ process::exit(1);
}
fn main() {
@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device")
+ device::Rtl8139::new(bar as usize).unwrap_or_else(|err| {
+ log::error!("rtl8139d: failed to allocate device: {err}");
+ process::exit(1);
+ })
},
daemon,
format!("network.{name}"),
@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("rtl8139d: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::new().unwrap_or_else(|err| {
+ log::error!("rtl8139d: failed to create event queue: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
irq_file.irq_handle().as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("rtl8139d: failed to subscribe to IRQ fd: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- libredox::call::setrens(0, 0).expect("rtl8139d: failed to enter null namespace");
-
- scheme.tick().unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("rtl8139d: failed to subscribe to scheme fd: {err}");
+ process::exit(1);
+ });
+
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ log::error!("rtl8139d: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
+
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8139d: failed initial scheme tick: {err}");
+ process::exit(1);
+ }
- for event in event_queue.map(|e| e.expect("rtl8139d: failed to get next event")) {
+ loop {
+ let event = match event_queue.next() {
+ Some(Ok(event)) => event,
+ Some(Err(err)) => {
+ log::error!("rtl8139d: failed to get next event: {err}");
+ continue;
+ }
+ None => break,
+ };
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.irq_handle().read(&mut irq).unwrap();
+ if let Err(err) = irq_file.irq_handle().read(&mut irq) {
+ log::error!("rtl8139d: failed to read IRQ: {err}");
+ continue;
+ }
//TODO: This may be causing spurious interrupts
if unsafe { scheme.adapter_mut().irq() } {
- irq_file.irq_handle().write(&mut irq).unwrap();
-
- scheme.tick().unwrap();
+ if let Err(err) = irq_file.irq_handle().write(&mut irq) {
+ log::error!("rtl8139d: failed to write IRQ: {err}");
+ continue;
+ }
+
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8139d: failed to handle IRQ tick: {err}");
+ }
}
}
Source::Scheme => {
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8139d: failed to handle scheme op: {err}");
+ }
}
}
}
- unreachable!()
+
+ process::exit(0);
}
diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs
index 1d9963a3..bd2fcb1a 100644
--- a/drivers/net/rtl8168d/src/main.rs
+++ b/drivers/net/rtl8168d/src/main.rs
@@ -1,5 +1,6 @@
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
+use std::process;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 {
other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other),
}
}
- panic!("rtl8168d: failed to find BAR");
+ log::error!("rtl8168d: failed to find BAR");
+ process::exit(1);
}
fn main() {
@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device")
+ device::Rtl8168::new(bar as usize).unwrap_or_else(|err| {
+ log::error!("rtl8168d: failed to allocate device: {err}");
+ process::exit(1);
+ })
},
daemon,
format!("network.{name}"),
@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("rtl8168d: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::new().unwrap_or_else(|err| {
+ log::error!("rtl8168d: failed to create event queue: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
irq_file.irq_handle().as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("rtl8168d: failed to subscribe to IRQ fd: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace");
-
- scheme.tick().unwrap();
+ .unwrap_or_else(|err| {
+ log::error!("rtl8168d: failed to subscribe to scheme fd: {err}");
+ process::exit(1);
+ });
+
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ log::error!("rtl8168d: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
+
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8168d: failed initial scheme tick: {err}");
+ process::exit(1);
+ }
- for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) {
+ loop {
+ let event = match event_queue.next() {
+ Some(Ok(event)) => event,
+ Some(Err(err)) => {
+ log::error!("rtl8168d: failed to get next event: {err}");
+ continue;
+ }
+ None => break,
+ };
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.irq_handle().read(&mut irq).unwrap();
+ if let Err(err) = irq_file.irq_handle().read(&mut irq) {
+ log::error!("rtl8168d: failed to read IRQ: {err}");
+ continue;
+ }
//TODO: This may be causing spurious interrupts
if unsafe { scheme.adapter_mut().irq() } {
- irq_file.irq_handle().write(&mut irq).unwrap();
-
- scheme.tick().unwrap();
+ if let Err(err) = irq_file.irq_handle().write(&mut irq) {
+ log::error!("rtl8168d: failed to write IRQ: {err}");
+ continue;
+ }
+
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8168d: failed to handle IRQ tick: {err}");
+ }
}
}
Source::Scheme => {
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ log::error!("rtl8168d: failed to handle scheme op: {err}");
+ }
}
}
}
- unreachable!()
+
+ process::exit(0);
}
diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs
index 17d168ef..adbd1086 100644
--- a/drivers/net/virtio-netd/src/main.rs
+++ b/drivers/net/virtio-netd/src/main.rs
@@ -3,6 +3,7 @@ mod scheme;
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
+use std::process;
use driver_network::NetworkScheme;
use pcid_interface::PciFunctionHandle;
@@ -31,8 +32,11 @@ fn main() {
}
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
- deamon(daemon, pcid_handle).unwrap();
- unreachable!();
+ deamon(daemon, pcid_handle).unwrap_or_else(|err| {
+ log::error!("virtio-netd: daemon failed: {err}");
+ process::exit(1);
+ });
+ process::exit(0);
}
fn deamon(
@@ -52,7 +56,10 @@ fn deamon(
// 0x1000 - virtio-net
let pci_config = pcid_handle.config();
- assert_eq!(pci_config.func.full_device_id.device_id, 0x1000);
+ if pci_config.func.full_device_id.device_id != 0x1000 {
+ log::error!("virtio-netd: unexpected device ID {:#06x}, expected 0x1000", pci_config.func.full_device_id.device_id);
+ process::exit(1);
+ }
log::info!("virtio-net: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
@@ -84,7 +91,8 @@ fn deamon(
device.transport.ack_driver_feature(VIRTIO_NET_F_MAC);
mac
} else {
- unimplemented!()
+ log::error!("virtio-netd: device does not support MAC feature");
+ return Err("virtio-netd: VIRTIO_NET_F_MAC not supported".into());
};
device.transport.finalize_features();
@@ -126,12 +134,23 @@ fn deamon(
data: 0,
})?;
- libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace");
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ log::error!("virtio-netd: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
- scheme.tick()?;
+ if let Err(err) = scheme.tick() {
+ log::error!("virtio-netd: failed initial scheme tick: {err}");
+ process::exit(1);
+ }
loop {
- event_queue.read(&mut [0; mem::size_of::<syscall::Event>()])?; // Wait for event
- scheme.tick()?;
+ if let Err(err) = event_queue.read(&mut [0; mem::size_of::<syscall::Event>()]) {
+ log::error!("virtio-netd: failed to read event: {err}");
+ continue;
+ }
+ if let Err(err) = scheme.tick() {
+ log::error!("virtio-netd: failed to handle scheme event: {err}");
+ }
}
@@ -1,118 +0,0 @@
# P2-network-error-handling.patch
#
# Network driver error handling: replace unwrap()/expect()/panic!() with proper
# error propagation and graceful exits across e1000, ixgbe, rtl8139, rtl8168d,
# and virtio-net drivers.
#
# Covers:
# - e1000d/device.rs: replace unreachable!() in DMA array conversion
# - ixgbed/Cargo.toml: add log dependency
# - rtl8139d/device.rs: replace unreachable!() with EIO error
# - rtl8168d/device.rs: replace unreachable!() with EIO error
# - virtio-netd/scheme.rs: DMA allocation error handling for rx buffers
#
diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs
index 4c518f30..0e42d72b 100644
--- a/drivers/net/e1000d/src/device.rs
+++ b/drivers/net/e1000d/src/device.rs
@@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time};
use driver_network::NetworkAdapter;
-use syscall::error::Result;
+use syscall::error::{Error, Result, EIO};
use common::dma::Dma;
@@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x {
}
fn dma_array<T, const N: usize>() -> Result<[Dma<T>; N]> {
- Ok((0..N)
+ let vec: Vec<Dma<T>> = (0..N)
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
- .collect::<Result<Vec<_>>>()?
- .try_into()
- .unwrap_or_else(|_| unreachable!()))
+ .collect::<Result<Vec<_>>>()?;
+ vec.try_into().map_err(|_| Error::new(EIO))
}
impl Intel8254x {
pub unsafe fn new(base: usize) -> Result<Self> {
diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml
index d97ff398..fcaf4b19 100644
--- a/drivers/net/ixgbed/Cargo.toml
+++ b/drivers/net/ixgbed/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
bitflags.workspace = true
libredox.workspace = true
+log.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs
index 37167ee2..d7428132 100644
--- a/drivers/net/rtl8139d/src/device.rs
+++ b/drivers/net/rtl8139d/src/device.rs
@@ -215,7 +215,7 @@ impl Rtl8139 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
transmit_i: 0,
mac_address: [0; 6],
};
diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs
index ae545ec4..7229a52d 100644
--- a/drivers/net/rtl8168d/src/device.rs
+++ b/drivers/net/rtl8168d/src/device.rs
@@ -177,7 +177,7 @@ impl Rtl8168 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
receive_ring: Dma::zeroed()?.assume_init(),
receive_i: 0,
@@ -185,7 +185,7 @@ impl Rtl8168 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
transmit_ring: Dma::zeroed()?.assume_init(),
transmit_i: 0,
transmit_buffer_h: [Dma::zeroed()?.assume_init()],
diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs
index 59b3b93e..d0acb2ba 100644
--- a/drivers/net/virtio-netd/src/scheme.rs
+++ b/drivers/net/virtio-netd/src/scheme.rs
@@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> {
// Populate all of the `rx_queue` with buffers to maximize performence.
let mut rx_buffers = vec![];
for i in 0..(rx.descriptor_len() as usize) {
- rx_buffers.push(unsafe {
- Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN)
- .unwrap()
- .assume_init()
- });
+ let buf = unsafe {
+ match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) {
+ Ok(dma) => dma.assume_init(),
+ Err(err) => {
+ log::error!("virtio-netd: failed to allocate rx buffer: {err}");
+ continue;
+ }
+ }
+ };
+ rx_buffers.push(buf);
let chain = ChainBuilder::new()
.chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY))
@@ -1,601 +0,0 @@
# P2-storage-error-handling.patch
#
# Storage driver error handling: replace unwrap()/expect()/panic!() with proper
# error propagation and graceful exits across AHCI, IDE, NVMe, and VirtIO block drivers.
#
# Covers:
# - ahcid/disk_ata.rs: replace unreachable!() with EIO error
# - ahcid/disk_atapi.rs: replace unreachable!() with EBADF error
# - ahcid/hba.rs: DMA allocation error handling
# - ided/ide.rs: assert→debug_assert, try_into error handling
# - nvmed/executor.rs: executor initialization error handling
# - nvmed/identify.rs: DMA allocation, unreachable!() fallback
# - nvmed/mod.rs: assert→debug_assert, unwrap→proper error/exit
# - nvmed/queues.rs: unreachable!()→safe fallback
# - virtio-blkd/scheme.rs: DMA allocation error handling, assert→if check
#
diff --git a/drivers/storage/ahcid/src/ahci/disk_ata.rs b/drivers/storage/ahcid/src/ahci/disk_ata.rs
index 4f83c51d..7423603b 100644
--- a/drivers/storage/ahcid/src/ahci/disk_ata.rs
+++ b/drivers/storage/ahcid/src/ahci/disk_ata.rs
@@ -1,7 +1,7 @@
use std::convert::TryInto;
use std::ptr;
-use syscall::error::Result;
+use syscall::error::{Error, Result, EIO};
use common::dma::Dma;
@@ -39,7 +39,7 @@ impl DiskATA {
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!());
+ .map_err(|_| Error::new(EIO))?;
let mut fb = unsafe { Dma::zeroed()?.assume_init() };
let buf = unsafe { Dma::zeroed()?.assume_init() };
diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs
index a0e75c09..8fbdfbef 100644
--- a/drivers/storage/ahcid/src/ahci/disk_atapi.rs
+++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs
@@ -37,7 +37,7 @@ impl DiskATAPI {
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!());
+ .map_err(|_| Error::new(EBADF))?;
let mut fb = unsafe { Dma::zeroed()?.assume_init() };
let mut buf = unsafe { Dma::zeroed()?.assume_init() };
diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs
index bea8792c..11a3d4ae 100644
--- a/drivers/storage/ahcid/src/ahci/hba.rs
+++ b/drivers/storage/ahcid/src/ahci/hba.rs
@@ -178,7 +178,10 @@ impl HbaPort {
clb: &mut Dma<[HbaCmdHeader; 32]>,
ctbas: &mut [Dma<HbaCmdTable>; 32],
) -> Result<u64> {
- let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap();
+ let dest: Dma<[u16; 256]> = Dma::new([0; 256]).map_err(|err| {
+ error!("ahcid: failed to allocate DMA buffer: {err}");
+ Error::new(EIO)
+ })?;
let slot = self
.ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| {
diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs
index 5faf3250..094e5889 100644
--- a/drivers/storage/ided/src/ide.rs
+++ b/drivers/storage/ided/src/ide.rs
@@ -184,10 +184,10 @@ impl Disk for AtaDisk {
let block = start_block + (count as u64) / 512;
//TODO: support other LBA modes
- assert!(block < 0x1_0000_0000_0000);
+ debug_assert!(block < 0x1_0000_0000_0000);
let sectors = (chunk.len() + 511) / 512;
- assert!(sectors <= 128);
+ debug_assert!(sectors <= 128);
log::trace!(
"IDE read chan {} dev {} block {:#x} count {:#x}",
@@ -205,7 +205,7 @@ impl Disk for AtaDisk {
// Make PRDT EOT match chunk size
for i in 0..sectors {
chan.prdt[i] = PrdtEntry {
- phys: (chan.buf.physical() + i * 512).try_into().unwrap(),
+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?,
size: 512,
flags: if i + 1 == sectors {
1 << 15 // End of table
@@ -216,7 +216,7 @@ impl Disk for AtaDisk {
}
// Set PRDT
let prdt = chan.prdt.physical();
- chan.busmaster_prdt.write(prdt.try_into().unwrap());
+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?);
// Set to read
chan.busmaster_command.writef(1 << 3, true);
// Clear interrupt and error bits
@@ -325,10 +325,10 @@ impl Disk for AtaDisk {
let block = start_block + (count as u64) / 512;
//TODO: support other LBA modes
- assert!(block < 0x1_0000_0000_0000);
+ debug_assert!(block < 0x1_0000_0000_0000);
let sectors = (chunk.len() + 511) / 512;
- assert!(sectors <= 128);
+ debug_assert!(sectors <= 128);
log::trace!(
"IDE write chan {} dev {} block {:#x} count {:#x}",
@@ -346,7 +346,7 @@ impl Disk for AtaDisk {
// Make PRDT EOT match chunk size
for i in 0..sectors {
chan.prdt[i] = PrdtEntry {
- phys: (chan.buf.physical() + i * 512).try_into().unwrap(),
+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?,
size: 512,
flags: if i + 1 == sectors {
1 << 15 // End of table
@@ -357,7 +357,7 @@ impl Disk for AtaDisk {
}
// Set PRDT
let prdt = chan.prdt.physical();
- chan.busmaster_prdt.write(prdt.try_into().unwrap());
+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?);
// Set to write
chan.busmaster_command.writef(1 << 3, false);
// Clear interrupt and error bits
diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs
index 6242fa98..c1435e88 100644
--- a/drivers/storage/nvmed/src/nvme/executor.rs
+++ b/drivers/storage/nvmed/src/nvme/executor.rs
@@ -34,7 +34,12 @@ impl Hardware for NvmeHw {
&VTABLE
}
fn current() -> std::rc::Rc<executor::LocalExecutor<Self>> {
- THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap()))
+ THE_EXECUTOR.with(|exec| {
+ Rc::clone(exec.borrow().as_ref().unwrap_or_else(|| {
+ log::error!("nvmed: internal error: executor not initialized");
+ std::process::exit(1);
+ }))
+ })
}
fn try_submit(
nvme: &Arc<Nvme>,
diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs
index 05e5b9b2..b1b6e959 100644
--- a/drivers/storage/nvmed/src/nvme/identify.rs
+++ b/drivers/storage/nvmed/src/nvme/identify.rs
@@ -126,7 +126,7 @@ impl LbaFormat {
0b01 => RelativePerformance::Better,
0b10 => RelativePerformance::Good,
0b11 => RelativePerformance::Degraded,
- _ => unreachable!(),
+ _ => RelativePerformance::Degraded,
}
}
pub fn is_available(&self) -> bool {
@@ -153,7 +153,14 @@ impl Nvme {
/// Returns the serial number, model, and firmware, in that order.
pub async fn identify_controller(&self) {
// TODO: Use same buffer
- let data: Dma<IdentifyControllerData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyControllerData> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate identify DMA: {err}");
+ std::process::exit(1);
+ })
+ };
// println!(" - Attempting to identify controller");
let comp = self
@@ -182,7 +189,14 @@ impl Nvme {
}
pub async fn identify_namespace_list(&self, base: u32) -> Vec<u32> {
// TODO: Use buffer
- let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<[u32; 1024]> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate namespace list DMA: {err}");
+ std::process::exit(1);
+ })
+ };
// println!(" - Attempting to retrieve namespace ID list");
let comp = self
@@ -198,7 +212,14 @@ impl Nvme {
}
pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace {
//TODO: Use buffer
- let data: Dma<IdentifyNamespaceData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyNamespaceData> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate namespace DMA: {err}");
+ std::process::exit(1);
+ })
+ };
log::debug!("Attempting to identify namespace {nsid}");
let comp = self
@@ -216,7 +237,10 @@ impl Nvme {
let block_size = data
.formatted_lba_size()
.lba_data_size()
- .expect("nvmed: error: size outside 512-2^64 range");
+ .unwrap_or_else(|| {
+ log::error!("nvmed: error: size outside 512-2^64 range");
+ std::process::exit(1);
+ });
log::debug!("NVME block size: {}", block_size);
NvmeNamespace {
diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs
index 682ee933..90a25d5b 100644
--- a/drivers/storage/nvmed/src/nvme/mod.rs
+++ b/drivers/storage/nvmed/src/nvme/mod.rs
@@ -160,7 +160,15 @@ impl Nvme {
}
fn cur_thread_ctxt(&self) -> Arc<ReentrantMutex<ThreadCtxt>> {
// TODO: multi-threading
- Arc::clone(self.thread_ctxts.read().get(&0).unwrap())
+ Arc::clone(
+ self.thread_ctxts
+ .read()
+ .get(&0)
+ .unwrap_or_else(|| {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ std::process::exit(1);
+ }),
+ )
}
pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) {
@@ -208,10 +216,22 @@ impl Nvme {
}
for (qid, iv) in self.cq_ivs.get_mut().iter_mut() {
- let ctxt = thread_ctxts.get(&0).unwrap().lock();
+ let ctxt = match thread_ctxts.get(&0) {
+ Some(c) => c.lock(),
+ None => {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ return Err(Error::new(EIO));
+ }
+ };
let queues = ctxt.queues.borrow();
- let &(ref cq, ref sq) = queues.get(qid).unwrap();
+ let (cq, sq) = match queues.get(qid) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: queue {qid} missing");
+ return Err(Error::new(EIO));
+ }
+ };
log::debug!(
"iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]",
cq.data.physical(),
@@ -222,7 +242,13 @@ impl Nvme {
}
{
- let main_ctxt = thread_ctxts.get(&0).unwrap().lock();
+ let main_ctxt = match thread_ctxts.get(&0) {
+ Some(c) => c.lock(),
+ None => {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ return Err(Error::new(EIO));
+ }
+ };
for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() {
*prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64;
@@ -231,7 +257,13 @@ impl Nvme {
let regs = self.regs.get_mut();
let mut queues = main_ctxt.queues.borrow_mut();
- let (asq, acq) = queues.get_mut(&0).unwrap();
+ let (asq, acq) = match queues.get_mut(&0) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: admin queue pair missing");
+ return Err(Error::new(EIO));
+ }
+ };
regs.aqa
.write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1));
regs.asq_low.write(asq.data.physical() as u32);
@@ -281,14 +313,14 @@ impl Nvme {
let vector = vector as u8;
if masked {
- assert_ne!(
+ debug_assert_ne!(
to_clear & (1 << vector),
(1 << vector),
"nvmed: internal error: cannot both mask and set"
);
to_mask |= 1 << vector;
} else {
- assert_ne!(
+ debug_assert_ne!(
to_mask & (1 << vector),
(1 << vector),
"nvmed: internal error: cannot both mask and set"
@@ -326,22 +358,27 @@ impl Nvme {
cmd_init: impl FnOnce(CmdId) -> NvmeCmd,
fail: impl FnOnce(),
) -> Option<(CqId, CmdId)> {
- match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() {
- (sq, _cq) => {
- if sq.is_full() {
- fail();
- return None;
- }
- let cmd_id = sq.tail;
- let tail = sq.submit_unchecked(cmd_init(cmd_id));
-
- // TODO: Submit in bulk
- unsafe {
- self.submission_queue_tail(sq_id, tail);
- }
- Some((sq_id, cmd_id))
+ let mut queues_ref = ctxt.queues.borrow_mut();
+ let (sq, _cq) = match queues_ref.get_mut(&sq_id) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: submission queue {sq_id} missing");
+ fail();
+ return None;
}
+ };
+ if sq.is_full() {
+ fail();
+ return None;
+ }
+ let cmd_id = sq.tail;
+ let tail = sq.submit_unchecked(cmd_init(cmd_id));
+
+ // TODO: Submit in bulk
+ unsafe {
+ self.submission_queue_tail(sq_id, tail);
}
+ Some((sq_id, cmd_id))
}
pub async fn create_io_completion_queue(
@@ -349,13 +386,19 @@ impl Nvme {
io_cq_id: CqId,
vector: Option<Iv>,
) -> NvmeCompQueue {
- let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue");
-
- let len = u16::try_from(queue.data.len())
- .expect("nvmed: internal error: I/O CQ longer than 2^16 entries");
- let raw_len = len
- .checked_sub(1)
- .expect("nvmed: internal error: CQID 0 for I/O CQ");
+ let queue = NvmeCompQueue::new().unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate I/O completion queue: {err}");
+ std::process::exit(1);
+ });
+
+ let len = u16::try_from(queue.data.len()).unwrap_or_else(|_| {
+ log::error!("nvmed: internal error: I/O CQ longer than 2^16 entries");
+ std::process::exit(1);
+ });
+ let raw_len = len.checked_sub(1).unwrap_or_else(|| {
+ log::error!("nvmed: internal error: CQID 0 for I/O CQ");
+ std::process::exit(1);
+ });
let comp = self
.submit_and_complete_admin_command(|cid| {
@@ -370,22 +413,28 @@ impl Nvme {
.await;
/*match comp.status.specific {
- 1 => panic!("invalid queue identifier"),
- 2 => panic!("invalid queue size"),
- 8 => panic!("invalid interrupt vector"),
+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); }
+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); }
+ 8 => { log::error!("nvmed: invalid interrupt vector"); std::process::exit(1); }
_ => (),
}*/
queue
}
pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue {
- let q = NvmeCmdQueue::new().expect("failed to create submission queue");
-
- let len = u16::try_from(q.data.len())
- .expect("nvmed: internal error: I/O SQ longer than 2^16 entries");
- let raw_len = len
- .checked_sub(1)
- .expect("nvmed: internal error: SQID 0 for I/O SQ");
+ let q = NvmeCmdQueue::new().unwrap_or_else(|err| {
+ log::error!("nvmed: failed to create submission queue: {err}");
+ std::process::exit(1);
+ });
+
+ let len = u16::try_from(q.data.len()).unwrap_or_else(|_| {
+ log::error!("nvmed: internal error: I/O SQ longer than 2^16 entries");
+ std::process::exit(1);
+ });
+ let raw_len = len.checked_sub(1).unwrap_or_else(|| {
+ log::error!("nvmed: internal error: SQID 0 for I/O SQ");
+ std::process::exit(1);
+ });
let comp = self
.submit_and_complete_admin_command(|cid| {
@@ -399,9 +448,9 @@ impl Nvme {
})
.await;
/*match comp.status.specific {
- 0 => panic!("completion queue invalid"),
- 1 => panic!("invalid queue identifier"),
- 2 => panic!("invalid queue size"),
+ 0 => { log::error!("nvmed: completion queue invalid"); std::process::exit(1); }
+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); }
+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); }
_ => (),
}*/
@@ -431,7 +480,10 @@ impl Nvme {
self.thread_ctxts
.read()
.get(&0)
- .unwrap()
+ .unwrap_or_else(|| {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ std::process::exit(1);
+ })
.lock()
.queues
.borrow_mut()
@@ -497,8 +549,8 @@ impl Nvme {
for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) {
let blocks = (chunk.len() + block_size - 1) / block_size;
- assert!(blocks > 0);
- assert!(blocks <= 0x1_0000);
+ debug_assert!(blocks > 0);
+ debug_assert!(blocks <= 0x1_0000);
self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false)
.await?;
@@ -525,8 +577,8 @@ impl Nvme {
for chunk in buf.chunks(/* TODO: buf len */ 8192) {
let blocks = (chunk.len() + block_size - 1) / block_size;
- assert!(blocks > 0);
- assert!(blocks <= 0x1_0000);
+ debug_assert!(blocks > 0);
+ debug_assert!(blocks <= 0x1_0000);
ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk);
diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs
index a3712aeb..438c905c 100644
--- a/drivers/storage/nvmed/src/nvme/queues.rs
+++ b/drivers/storage/nvmed/src/nvme/queues.rs
@@ -145,7 +145,7 @@ impl Status {
3 => Self::PathRelatedStatus(code),
4..=6 => Self::Rsvd(code),
7 => Self::Vendor(code),
- _ => unreachable!(),
+ _ => Self::Vendor(code),
}
}
}
diff --git a/drivers/storage/virtio-blkd/src/scheme.rs b/drivers/storage/virtio-blkd/src/scheme.rs
index ec4ecf73..39fb24a8 100644
--- a/drivers/storage/virtio-blkd/src/scheme.rs
+++ b/drivers/storage/virtio-blkd/src/scheme.rs
@@ -15,19 +15,34 @@ trait BlkExtension {
impl BlkExtension for Queue<'_> {
async fn read(&self, block: u64, target: &mut [u8]) -> usize {
- let req = Dma::new(BlockVirtRequest {
+ let req = match Dma::new(BlockVirtRequest {
ty: BlockRequestTy::In,
reserved: 0,
sector: block,
- })
- .unwrap();
+ }) {
+ Ok(req) => req,
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate read request DMA: {err}");
+ return 0;
+ }
+ };
let result = unsafe {
- Dma::<[u8]>::zeroed_slice(target.len())
- .unwrap()
- .assume_init()
+ match Dma::<[u8]>::zeroed_slice(target.len()) {
+ Ok(dma) => dma.assume_init(),
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate read buffer DMA: {err}");
+ return 0;
+ }
+ }
+ };
+ let status = match Dma::new(u8::MAX) {
+ Ok(s) => s,
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate read status DMA: {err}");
+ return 0;
+ }
};
- let status = Dma::new(u8::MAX).unwrap();
let chain = ChainBuilder::new()
.chain(Buffer::new(&req))
@@ -37,28 +52,46 @@ impl BlkExtension for Queue<'_> {
// XXX: Subtract 1 because the of status byte.
let written = self.send(chain).await as usize - 1;
- assert_eq!(*status, 0);
+ if *status != 0 {
+ log::error!("virtio-blkd: read failed with status {}", *status);
+ return 0;
+ }
target[..written].copy_from_slice(&result);
written
}
async fn write(&self, block: u64, target: &[u8]) -> usize {
- let req = Dma::new(BlockVirtRequest {
+ let req = match Dma::new(BlockVirtRequest {
ty: BlockRequestTy::Out,
reserved: 0,
sector: block,
- })
- .unwrap();
+ }) {
+ Ok(req) => req,
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate write request DMA: {err}");
+ return 0;
+ }
+ };
let mut result = unsafe {
- Dma::<[u8]>::zeroed_slice(target.len())
- .unwrap()
- .assume_init()
+ match Dma::<[u8]>::zeroed_slice(target.len()) {
+ Ok(dma) => dma.assume_init(),
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate write buffer DMA: {err}");
+ return 0;
+ }
+ }
};
result.copy_from_slice(target.as_ref());
- let status = Dma::new(u8::MAX).unwrap();
+ let status = match Dma::new(u8::MAX) {
+ Ok(s) => s,
+ Err(err) => {
+ log::error!("virtio-blkd: failed to allocate write status DMA: {err}");
+ return 0;
+ }
+ };
let chain = ChainBuilder::new()
.chain(Buffer::new(&req))
@@ -67,7 +100,10 @@ impl BlkExtension for Queue<'_> {
.build();
self.send(chain).await as usize;
- assert_eq!(*status, 0);
+ if *status != 0 {
+ log::error!("virtio-blkd: write failed with status {}", *status);
+ return 0;
+ }
target.len()
}
@@ -1,158 +0,0 @@
# P2-usb-pm-and-drivers.patch
#
# USB power management and driver interface improvements:
# suspend/resume commands, SCSI driver enablement, PortPmState type,
# IRQ reactor staged port state fallback.
#
# Covers:
# - usbctl/main.rs: pm-state, suspend, resume subcommands
# - xhcid/drivers.toml: enable SCSI over USB driver (was commented out)
# - xhcid/driver_interface.rs: PortPmState enum, suspend/resume/port_pm_state methods
# - xhcid/irq_reactor.rs: staged_port_states fallback in with_ring/with_ring_mut
#
diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs
index 9b5773d9..232f7cfc 100644
--- a/drivers/usb/usbctl/src/main.rs
+++ b/drivers/usb/usbctl/src/main.rs
@@ -15,6 +15,9 @@ fn main() {
Command::new("port")
.arg(Arg::new("PORT").num_args(1).required(true))
.subcommand(Command::new("status"))
+ .subcommand(Command::new("pm-state"))
+ .subcommand(Command::new("suspend"))
+ .subcommand(Command::new("resume"))
.subcommand(
Command::new("endpoint")
.arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true))
@@ -38,6 +41,15 @@ fn main() {
if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") {
let state = handle.port_state().expect("Failed to get port state");
println!("{}", state.as_str());
+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") {
+ let state = handle
+ .port_pm_state()
+ .expect("Failed to get port power-management state");
+ println!("{}", state.as_str());
+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") {
+ handle.suspend_device().expect("Failed to suspend device");
+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") {
+ handle.resume_device().expect("Failed to resume device");
} else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") {
let endp_num = endp_scmd_matches
.get_one::<String>("ENDPOINT_NUM")
diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml
index 83c90e23..470ec063 100644
--- a/drivers/usb/xhcid/drivers.toml
+++ b/drivers/usb/xhcid/drivers.toml
@@ -1,9 +1,8 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
-#class = 8 # Mass Storage class
-#subclass = 6 # SCSI transparent command set
-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
+[[drivers]]
+name = "SCSI over USB"
+class = 8 # Mass Storage class
+subclass = 6 # SCSI transparent command set
+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
[[drivers]]
name = "USB HUB"
diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs
index 727f8d7e..82f839ae 100644
--- a/drivers/usb/xhcid/src/driver_interface.rs
+++ b/drivers/usb/xhcid/src/driver_interface.rs
@@ -444,6 +444,33 @@ impl str::FromStr for PortState {
}
}
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
+}
+
+impl str::FromStr for PortPmState {
+ type Err = Invalid;
+
+ fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+ Ok(match s {
+ "active" => Self::Active,
+ "suspended" => Self::Suspended,
+ _ => return Err(Invalid("read reserved port PM state")),
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum EndpointStatus {
@@ -560,6 +587,16 @@ impl XhciClientHandle {
let _bytes_written = file.write(&[])?;
Ok(())
}
+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
let json = self.read("descriptors")?;
Ok(serde_json::from_slice(&json)?)
@@ -582,6 +619,10 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
+ Ok(string.parse()?)
+ }
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
let path = format!("endpoints/{}/ctl", num);
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
index ac492d5b..310fe51f 100644
--- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs
+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
@@ -633,7 +633,10 @@ impl<const N: usize> Xhci<N> {
pub fn with_ring<T, F: FnOnce(&Ring) -> T>(&self, id: RingId, function: F) -> Option<T> {
use super::RingOrStreams;
- let slot_state = self.port_states.get(&id.port)?;
+ let slot_state = self
+ .port_states
+ .get(&id.port)
+ .or_else(|| self.staged_port_states.get(&id.port))?;
let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
@@ -650,7 +653,10 @@ impl<const N: usize> Xhci<N> {
) -> Option<T> {
use super::RingOrStreams;
- let mut slot_state = self.port_states.get_mut(&id.port)?;
+ let mut slot_state = self
+ .port_states
+ .get_mut(&id.port)
+ .or_else(|| self.staged_port_states.get_mut(&id.port))?;
let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
@@ -1,398 +0,0 @@
--- a/drivers/pcid/src/cfg_access/mod.rs
+++ b/drivers/pcid/src/cfg_access/mod.rs
@@ -349,6 +349,10 @@
let bus_addr = self.bus_addr(address.segment(), address.bus())?;
Some(unsafe { bus_addr.add(Self::bus_addr_offset_in_dwords(address, offset)) })
}
+
+ pub fn has_extended_config(&self, address: PciAddress) -> bool {
+ self.mmio_addr(address, 0x100).is_some()
+ }
}
impl ConfigRegionAccess for Pcie {
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -5,12 +5,61 @@
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
-use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY};
+use syscall::error::{
+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS,
+};
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
use syscall::schemev2::NewFdFlags;
use syscall::ENOLCK;
use crate::cfg_access::Pcie;
+
+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001;
+
+#[derive(Clone, Copy)]
+enum AerRegisterName {
+ UncorStatus,
+ UncorMask,
+ UncorSeverity,
+ CorStatus,
+ CorMask,
+ Cap,
+ HeaderLog,
+}
+
+impl AerRegisterName {
+ fn from_path(path: &str) -> Option<Self> {
+ Some(match path {
+ "uncor_status" => Self::UncorStatus,
+ "uncor_mask" => Self::UncorMask,
+ "uncor_severity" => Self::UncorSeverity,
+ "cor_status" => Self::CorStatus,
+ "cor_mask" => Self::CorMask,
+ "cap" => Self::Cap,
+ "header_log" => Self::HeaderLog,
+ _ => return None,
+ })
+ }
+
+ const fn offset(self) -> u16 {
+ match self {
+ Self::UncorStatus => 0x00,
+ Self::UncorMask => 0x04,
+ Self::UncorSeverity => 0x08,
+ Self::CorStatus => 0x0C,
+ Self::CorMask => 0x10,
+ Self::Cap => 0x14,
+ Self::HeaderLog => 0x18,
+ }
+ }
+
+ const fn len(self) -> usize {
+ match self {
+ Self::HeaderLog => 16,
+ _ => 4,
+ }
+ }
+}
pub struct PciScheme {
handles: HandleMap<HandleWrapper>,
@@ -20,13 +69,27 @@
binds: HashMap<String, u32>,
}
enum Handle {
- TopLevel { entries: Vec<String> },
+ TopLevel {
+ entries: Vec<String>,
+ },
Access,
- Device,
- Channel { addr: PciAddress, st: ChannelState },
+ Device {
+ addr: PciAddress,
+ },
+ Channel {
+ addr: PciAddress,
+ st: ChannelState,
+ },
SchemeRoot,
/// Represents an open handle to a device's bind endpoint
- Bind { addr: PciAddress },
+ Bind {
+ addr: PciAddress,
+ },
+ AerDir,
+ Aer {
+ addr: PciAddress,
+ register: AerRegisterName,
+ },
/// Uevent surface for hotplug consumers. Opening uevent returns an object
/// from which device add/remove events can be read. Since pcid currently
/// only scans at startup, this surface is ready for hotplug polling consumers.
@@ -38,13 +101,23 @@
}
impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
+ matches!(
+ self,
+ Self::Access
+ | Self::Channel { .. }
+ | Self::Bind { .. }
+ | Self::Aer { .. }
+ | Self::Uevent
+ )
}
fn is_dir(&self) -> bool {
!self.is_file()
}
fn requires_root(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. })
+ matches!(
+ self,
+ Self::Access | Self::Channel { .. } | Self::Bind { .. }
+ )
}
fn is_scheme_root(&self) -> bool {
matches!(self, Self::SchemeRoot)
@@ -57,6 +130,16 @@
}
const DEVICE_CONTENTS: &[&str] = &["channel", "bind"];
+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"];
+const AER_CONTENTS: &[&str] = &[
+ "uncor_status",
+ "uncor_mask",
+ "uncor_severity",
+ "cor_status",
+ "cor_mask",
+ "cap",
+ "header_log",
+];
impl PciScheme {
pub fn access(&mut self) -> usize {
@@ -141,7 +224,12 @@
let (len, mode) = match handle.inner {
Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755),
- Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Device { addr } => (
+ Self::device_entries(&self.pcie, addr).len(),
+ MODE_DIR | 0o755,
+ ),
+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444),
Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
@@ -154,7 +242,7 @@
&mut self,
id: usize,
buf: &mut [u8],
- _offset: u64,
+ offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
@@ -166,11 +254,14 @@
match handle.inner {
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
- Handle::Device => Err(Error::new(EISDIR)),
+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)),
Handle::Channel {
addr: _,
ref mut st,
} => Self::read_channel(st, buf),
+ Handle::Aer { addr, register } => {
+ Self::read_aer_register(&self.pcie, addr, register, buf, offset)
+ }
Handle::Uevent => {
// Uevent surface is ready for hotplug polling consumers.
// pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available).
@@ -209,8 +300,15 @@
}
return Ok(buf);
}
- Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)),
+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr),
+ Handle::AerDir => AER_CONTENTS,
+ Handle::Access
+ | Handle::Channel { .. }
+ | Handle::Bind { .. }
+ | Handle::Aer { .. }
+ | Handle::Uevent => {
+ return Err(Error::new(ENOTDIR));
+ }
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -243,6 +341,7 @@
Handle::Channel { addr, ref mut st } => {
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
}
+ Handle::Aer { .. } => Err(Error::new(EROFS)),
_ => Err(Error::new(EBADF)),
}
@@ -357,45 +456,151 @@
binds: HashMap::new(),
}
}
- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result<Handle> {
+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] {
+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() {
+ DEVICE_AER_CONTENTS
+ } else {
+ DEVICE_CONTENTS
+ }
+ }
+ fn find_pcie_extended_capability(
+ pcie: &Pcie,
+ addr: PciAddress,
+ capability_id: u16,
+ ) -> Option<u16> {
+ if !pcie.has_extended_config(addr) {
+ return None;
+ }
+
+ let mut offset = 0x100_u16;
+
+ while offset <= 0xFFC {
+ let header = unsafe { pcie.read(addr, offset) };
+ if header == 0 || header == u32::MAX {
+ return None;
+ }
+
+ if (header & 0xFFFF) as u16 == capability_id {
+ return Some(offset);
+ }
+
+ let next = ((header >> 20) & 0xFFF) as u16;
+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 {
+ return None;
+ }
+ offset = next;
+ }
+
+ None
+ }
+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result<usize> {
+ let Ok(offset) = usize::try_from(offset) else {
+ return Ok(0);
+ };
+ if offset >= data.len() {
+ return Ok(0);
+ }
+
+ let count = std::cmp::min(buf.len(), data.len() - offset);
+ buf[..count].copy_from_slice(&data[offset..offset + count]);
+ Ok(count)
+ }
+ fn read_aer_register(
+ pcie: &Pcie,
+ addr: PciAddress,
+ register: AerRegisterName,
+ buf: &mut [u8],
+ offset: u64,
+ ) -> Result<usize> {
+ let Some(aer_base) =
+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER)
+ else {
+ return Err(Error::new(ENOENT));
+ };
+
+ let mut data = [0_u8; 16];
+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() {
+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?;
+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) };
+ chunk.copy_from_slice(&value.to_le_bytes());
+ }
+
+ Self::read_file_bytes(&data[..register.len()], buf, offset)
+ }
+ fn parse_after_pci_addr(
+ &mut self,
+ addr: PciAddress,
+ after: &str,
+ ctx: &CallerCtx,
+ ) -> Result<Handle> {
if after.chars().next().map_or(false, |c| c != '/') {
return Err(Error::new(ENOENT));
}
let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
Ok(if after.is_empty() {
- Handle::Device
+ Handle::Device { addr }
} else {
let path = &after[1..];
- match path {
- "channel" => {
- if func.enabled {
- return Err(Error::new(ENOLCK));
+ if path == "aer" {
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::AerDir
+ } else if let Some(register_name) = path.strip_prefix("aer/") {
+ let register =
+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?;
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::Aer { addr, register }
+ } else {
+ match path {
+ "channel" => {
+ if func.enabled {
+ return Err(Error::new(ENOLCK));
+ }
+ func.inner.legacy_interrupt_line = crate::enable_function(
+ &self.pcie,
+ &mut func.endpoint_header,
+ &mut func.capabilities,
+ );
+ func.enabled = true;
+ Handle::Channel {
+ addr,
+ st: ChannelState::AwaitingData,
+ }
}
- func.inner.legacy_interrupt_line = crate::enable_function(
- &self.pcie,
- &mut func.endpoint_header,
- &mut func.capabilities,
- );
- func.enabled = true;
- Handle::Channel {
- addr,
- st: ChannelState::AwaitingData,
+ "bind" => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!(
+ "pcid: device {} already bound by pid {}",
+ addr_str,
+ owner_pid
+ );
+ return Err(Error::new(EALREADY));
+ }
+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?;
+ self.binds.insert(addr_str.clone(), caller_pid);
+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
+ Handle::Bind { addr }
}
- }
- "bind" => {
- let addr_str = format!("{}", addr);
- if let Some(&owner_pid) = self.binds.get(&addr_str) {
- log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid);
- return Err(Error::new(EALREADY));
- }
- let caller_pid = ctx.pid;
- self.binds.insert(addr_str.clone(), caller_pid);
- log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
- Handle::Bind { addr }
- }
- _ => return Err(Error::new(ENOENT)),
+ _ => return Err(Error::new(ENOENT)),
+ }
}
})
}
@@ -1,97 +0,0 @@
diff --git a/src/main.rs b/src/main.rs
index b2e2736..a6a9474 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -500,33 +500,62 @@ pub extern "C" fn main() -> ! {
print!("live: 0/{} MiB", size / MIBI as u64);
- let ptr = os.alloc_zeroed_page_aligned(size as usize);
- if ptr.is_null() {
- panic!("Failed to allocate memory for live");
- }
-
- let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
-
- let mut i = 0;
- for chunk in live.chunks_mut(MIBI) {
- print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
- i += unsafe {
- fs.disk
- .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk)
- .expect("Failed to read live disk") as u64
- };
- }
- println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
-
- println!("Switching to live disk");
- unsafe {
- LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize)));
- }
+ let live_size = match usize::try_from(size) {
+ Ok(live_size) => live_size,
+ Err(_) => {
+ println!("\rlive: disabled (image too large for bootloader address space)");
+ live = false;
+ 0
+ }
+ };
- area_add(OsMemoryEntry {
- base: live.as_ptr() as u64,
- size: live.len() as u64,
- kind: OsMemoryKind::Reserved,
- });
+ let ptr = if live {
+ os.alloc_zeroed_page_aligned(live_size)
+ } else {
+ ptr::null_mut()
+ };
+
+ if live && ptr.is_null() {
+ println!(
+ "\rlive: disabled (unable to allocate {} MiB upfront)",
+ size / MIBI as u64
+ );
+ live = false;
+ }
+
+ let live = if live {
+ Some(unsafe { slice::from_raw_parts_mut(ptr, live_size) })
+ } else {
+ println!("Continuing without live preload");
+ None
+ };
+
+ if let Some(live) = live {
+ let mut i = 0;
+ for chunk in live.chunks_mut(MIBI) {
+ print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
+ i += unsafe {
+ fs.disk
+ .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk)
+ .expect("Failed to read live disk") as u64
+ };
+ }
+ println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
+
+ println!("Switching to live disk");
+ unsafe {
+ LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, live_size)));
+ }
+
+ area_add(OsMemoryEntry {
+ base: live.as_ptr() as u64,
+ size: live.len() as u64,
+ kind: OsMemoryKind::Reserved,
+ });
+
+ Some(live)
+ } else {
+ None
+ }
-
- Some(live)
} else {
None
};
@@ -1,60 +0,0 @@
diff --git a/src/os/uefi/device.rs b/src/os/uefi/device.rs
index 4b0bf31..90a97b8 100644
--- a/src/os/uefi/device.rs
+++ b/src/os/uefi/device.rs
@@ -46,6 +46,8 @@ fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePath
}
fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option<Vec<u8>> {
+ const MAX_LIVE_IMAGE_PRELOAD: usize = 128 * 1024 * 1024;
+
let mut esp_fs = match FileSystem::handle_protocol(esp_handle) {
Ok(esp_fs) => esp_fs,
Err(err) => {
@@ -87,9 +89,37 @@ fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option<V
};
let mut buffer = Vec::new();
+ let mut chunk = [0_u8; 64 * 1024];
+
+ loop {
+ let read = match live_image.read(&mut chunk) {
+ Ok(read) => read,
+ Err(err) => {
+ log::warn!(
+ "Failed while reading {}\\redox-live.iso: {:?}",
+ device_path_to_string(esp_device_path),
+ err
+ );
+ return None;
+ }
+ };
+
+ if read == 0 {
+ break;
+ }
- live_image.read_to_end(&mut buffer).unwrap();
+ if buffer.len().saturating_add(read) > MAX_LIVE_IMAGE_PRELOAD {
+ log::warn!(
+ "Skipping {}\\redox-live.iso preload: file exceeds {} MiB safety limit",
+ device_path_to_string(esp_device_path),
+ MAX_LIVE_IMAGE_PRELOAD / 1024 / 1024
+ );
+ return None;
+ }
+
+ buffer.extend_from_slice(&chunk[..read]);
+ }
Some(buffer)
}
@@ -130,7 +160,7 @@ pub fn disk_device_priority() -> Vec<DiskDevice> {
return vec![DiskDevice {
handle: esp_handle,
// Support both a copy of livedisk.iso and a standalone redoxfs partition
- partition_offset: if &buffer[512..520] == b"EFI PART" {
+ partition_offset: if buffer.len() >= 520 && &buffer[512..520] == b"EFI PART" {
//TODO: get block from partition table
2 * crate::MIBI as u64
} else {
@@ -1,392 +0,0 @@
diff --git a/src/arch/x86/mod.rs b/src/arch/x86/mod.rs
index bda3f5d..55889df 100644
--- a/src/arch/x86/mod.rs
+++ b/src/arch/x86/mod.rs
@@ -3,10 +3,15 @@ use crate::os::Os;
pub(crate) mod x32;
pub(crate) mod x64;
-pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
+pub unsafe fn paging_create(
+ os: &impl Os,
+ kernel_phys: u64,
+ kernel_size: u64,
+ identity_map_end: u64,
+) -> Option<usize> {
unsafe {
if crate::KERNEL_64BIT {
- x64::paging_create(os, kernel_phys, kernel_size)
+ x64::paging_create(os, kernel_phys, kernel_size, identity_map_end)
} else {
x32::paging_create(os, kernel_phys, kernel_size)
}
diff --git a/src/arch/x86/x64.rs b/src/arch/x86/x64.rs
index a0a275a..fcf309d 100644
--- a/src/arch/x86/x64.rs
+++ b/src/arch/x86/x64.rs
@@ -29,7 +29,12 @@ const PRESENT: u64 = 1;
const WRITABLE: u64 = 1 << 1;
const LARGE: u64 = 1 << 7;
-pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
+pub unsafe fn paging_create(
+ os: &impl Os,
+ kernel_phys: u64,
+ kernel_size: u64,
+ identity_map_end: u64,
+) -> Option<usize> {
unsafe {
// Create PML4
let pml4 = paging_allocate(os)?;
@@ -42,8 +47,14 @@ pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -
pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
- // Identity map 8 GiB using 2 MiB pages
- for pdp_i in 0..8 {
+ let mut needed_pdp = identity_map_end.div_ceil(0x4000_0000);
+ if needed_pdp == 0 {
+ needed_pdp = 1;
+ }
+ assert!(needed_pdp <= pdp.len() as u64, "identity map end exceeds paging span");
+
+ // Identity map required physical range using 2 MiB pages
+ for pdp_i in 0..needed_pdp as usize {
let pd = paging_allocate(os)?;
pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
for pd_i in 0..pd.len() {
diff --git a/src/main.rs b/src/main.rs
index 78dabb0..fd8eb81 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -62,6 +62,10 @@ pub static mut KERNEL_64BIT: bool = false;
pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None;
+fn region_end(base: u64, size: u64) -> u64 {
+ base.saturating_add(size).next_multiple_of(0x1000)
+}
+
struct SliceWriter<'a> {
slice: &'a mut [u8],
i: usize,
@@ -645,9 +649,6 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
(memory.len() as u64, memory.as_mut_ptr() as u64)
};
- let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) }
- .expect("Failed to set up paging");
-
let max_env_size = 64 * KIBI;
let mut env_size = max_env_size;
let env_base = os.alloc_zeroed_page_aligned(env_size);
@@ -655,6 +656,28 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
panic!("Failed to allocate memory for stack");
}
+ let mut identity_map_end = region_end(kernel.as_ptr() as u64, kernel.len() as u64)
+ .max(region_end(stack_base as u64, stack_size as u64))
+ .max(region_end(bootstrap_base, bootstrap_size))
+ .max(region_end(env_base as u64, max_env_size as u64));
+
+ if let Some(ref live) = live_opt {
+ identity_map_end = identity_map_end.max(region_end(
+ live.as_ptr() as u64,
+ live.len() as u64,
+ ));
+ }
+
+ let page_phys = unsafe {
+ paging_create(
+ os,
+ kernel.as_ptr() as u64,
+ kernel.len() as u64,
+ identity_map_end,
+ )
+ }
+ .expect("Failed to set up paging");
+
{
let mut w = SliceWriter {
slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) },
diff --git a/src/os/uefi/device.rs b/src/os/uefi/device.rs
index 0b7991f..554d88e 100644
--- a/src/os/uefi/device.rs
+++ b/src/os/uefi/device.rs
@@ -13,6 +13,160 @@ use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol};
use super::disk::{DiskEfi, DiskOrFileEfi};
+#[derive(Clone, Copy)]
+struct GptPartitionInfo {
+ first_lba: u64,
+ last_lba: u64,
+}
+
+fn read_u32_le(bytes: &[u8]) -> Option<u32> {
+ Some(u32::from_le_bytes(bytes.get(..4)?.try_into().ok()?))
+}
+
+fn read_u64_le(bytes: &[u8]) -> Option<u64> {
+ Some(u64::from_le_bytes(bytes.get(..8)?.try_into().ok()?))
+}
+
+fn decode_utf16_name(bytes: &[u8]) -> Option<String> {
+ let mut units = Vec::new();
+ for chunk in bytes.chunks_exact(2) {
+ let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
+ if unit == 0 {
+ break;
+ }
+ units.push(unit);
+ }
+ String::from_utf16(&units).ok()
+}
+
+fn select_partition(best: &mut Option<GptPartitionInfo>, candidate: GptPartitionInfo) {
+ match best {
+ Some(current) if current.last_lba.saturating_sub(current.first_lba) >= candidate.last_lba.saturating_sub(candidate.first_lba) => {}
+ _ => *best = Some(candidate),
+ }
+}
+
+fn parse_gpt_partition_offset_from_bytes(data: &[u8], block_size: usize) -> Option<u64> {
+ let header_offset = block_size;
+ let header = data.get(header_offset..header_offset + 92)?;
+ if header.get(..8)? != b"EFI PART" {
+ return None;
+ }
+
+ let entries_lba = read_u64_le(header.get(72..80)?)?;
+ let entry_count = read_u32_le(header.get(80..84)?)? as usize;
+ let entry_size = read_u32_le(header.get(84..88)?)? as usize;
+ if entry_size < 128 {
+ return None;
+ }
+
+ let entries_offset = entries_lba.checked_mul(block_size as u64)? as usize;
+ let mut redox_partition = None;
+ let mut fallback_partition = None;
+
+ for index in 0..entry_count {
+ let entry_offset = entries_offset.checked_add(index.checked_mul(entry_size)?)?;
+ let entry = data.get(entry_offset..entry_offset + entry_size)?;
+ if entry.get(..16)?.iter().all(|byte| *byte == 0) {
+ continue;
+ }
+
+ let first_lba = read_u64_le(entry.get(32..40)?)?;
+ let last_lba = read_u64_le(entry.get(40..48)?)?;
+ if first_lba == 0 || last_lba < first_lba {
+ continue;
+ }
+
+ let partition = GptPartitionInfo { first_lba, last_lba };
+ let name = decode_utf16_name(entry.get(56..128)?).unwrap_or_default();
+ if name == "REDOX" {
+ redox_partition = Some(partition);
+ break;
+ }
+
+ select_partition(&mut fallback_partition, partition);
+ }
+
+ redox_partition
+ .or(fallback_partition)
+ .map(|partition| partition.first_lba * block_size as u64)
+}
+
+fn parse_gpt_partition_offset_from_parts(
+ entries: &[u8],
+ entry_count: usize,
+ entry_size: usize,
+ block_size: usize,
+) -> Option<u64> {
+ let mut redox_partition = None;
+ let mut fallback_partition = None;
+
+ for index in 0..entry_count {
+ let entry_offset = index.checked_mul(entry_size)?;
+ let entry = entries.get(entry_offset..entry_offset + entry_size)?;
+ if entry.get(..16)?.iter().all(|byte| *byte == 0) {
+ continue;
+ }
+
+ let first_lba = read_u64_le(entry.get(32..40)?)?;
+ let last_lba = read_u64_le(entry.get(40..48)?)?;
+ if first_lba == 0 || last_lba < first_lba {
+ continue;
+ }
+
+ let partition = GptPartitionInfo { first_lba, last_lba };
+ let name = decode_utf16_name(entry.get(56..128)?).unwrap_or_default();
+ if name == "REDOX" {
+ redox_partition = Some(partition);
+ break;
+ }
+
+ select_partition(&mut fallback_partition, partition);
+ }
+ redox_partition
+ .or(fallback_partition)
+ .map(|partition| partition.first_lba * block_size as u64)
+}
+
+fn gpt_partition_offset_from_buffer(data: &[u8]) -> Option<u64> {
+ parse_gpt_partition_offset_from_bytes(data, 512)
+}
+
+fn gpt_partition_offset_from_disk(disk: &mut DiskEfi) -> Option<u64> {
+ const GPT_SECTOR_SIZE: usize = 512;
+
+ if disk.media_block_size() == 0 {
+ return None;
+ }
+
+ let mut boot_region = vec![0_u8; 2048];
+ disk.read_bytes(0, &mut boot_region).ok()?;
+ let header = boot_region.get(GPT_SECTOR_SIZE..GPT_SECTOR_SIZE + 92)?;
+ if header.get(..8)? != b"EFI PART" {
+ return None;
+ }
+
+ let entries_lba = read_u64_le(header.get(72..80)?)?;
+ let entry_count = read_u32_le(header.get(80..84)?)? as usize;
+ let entry_size = read_u32_le(header.get(84..88)?)? as usize;
+ if entry_size < 128 {
+ return None;
+ }
+
+ let entries_bytes = entry_count.checked_mul(entry_size)?;
+ let entries_offset = entries_lba.checked_mul(GPT_SECTOR_SIZE as u64)?;
+ let mut entries = vec![0_u8; entries_bytes];
+ disk.read_bytes(entries_offset, &mut entries).ok()?;
+
+ parse_gpt_partition_offset_from_parts(&entries, entry_count, entry_size, GPT_SECTOR_SIZE)
+}
+
#[derive(Debug)]
enum DevicePathRelation {
This,
@@ -131,12 +285,7 @@ pub fn disk_device_priority() -> Vec<DiskDevice> {
return vec![DiskDevice {
handle: esp_handle,
// Support both a copy of livedisk.iso and a standalone redoxfs partition
- partition_offset: if &buffer[512..520] == b"EFI PART" {
- //TODO: get block from partition table
- 2 * crate::MIBI as u64
- } else {
- 0
- },
+ partition_offset: gpt_partition_offset_from_buffer(&buffer).unwrap_or(0),
disk: DiskOrFileEfi::File(buffer),
device_path: esp_device_path,
file_path: Some("redox-live.iso"),
@@ -154,7 +303,7 @@ pub fn disk_device_priority() -> Vec<DiskDevice> {
};
let mut devices = Vec::with_capacity(handles.len());
for handle in handles {
- let disk = match DiskEfi::handle_protocol(handle) {
+ let mut disk = match DiskEfi::handle_protocol(handle) {
Ok(ok) => ok,
Err(err) => {
log::warn!(
@@ -182,14 +331,15 @@ pub fn disk_device_priority() -> Vec<DiskDevice> {
}
};
+ let partition_offset = if disk.0.Media.LogicalPartition {
+ 0
+ } else {
+ gpt_partition_offset_from_disk(&mut disk).unwrap_or(2 * crate::MIBI as u64)
+ };
+
devices.push(DiskDevice {
handle,
- partition_offset: if disk.0.Media.LogicalPartition {
- 0
- } else {
- //TODO: get block from partition table
- 2 * crate::MIBI as u64
- },
+ partition_offset,
disk: DiskOrFileEfi::Disk(disk),
device_path,
file_path: None,
diff --git a/src/os/uefi/disk.rs b/src/os/uefi/disk.rs
index 3f920bb..4d109f8 100644
--- a/src/os/uefi/disk.rs
+++ b/src/os/uefi/disk.rs
@@ -117,3 +117,43 @@ impl Disk for DiskEfi {
Err(Error::new(EIO))
}
}
+
+impl DiskEfi {
+ pub fn media_block_size(&self) -> usize {
+ self.0.Media.BlockSize as usize
+ }
+
+ pub fn read_bytes(&mut self, offset: u64, buffer: &mut [u8]) -> Result<()> {
+ let block_size = self.media_block_size();
+ if block_size == 0 || block_size > self.1.len() {
+ return Err(Error::new(EINVAL));
+ }
+
+ let scratch = &mut self.1[..block_size];
+ let mut copied = 0usize;
+
+ while copied < buffer.len() {
+ let absolute = offset as usize + copied;
+ let lba = (absolute / block_size) as u64;
+ let in_block = absolute % block_size;
+
+ match (self.0.ReadBlocks)(
+ self.0,
+ self.0.Media.MediaId,
+ lba,
+ block_size,
+ scratch.as_mut_ptr(),
+ ) {
+ status if status.is_success() => {
+ let chunk_len = core::cmp::min(block_size - in_block, buffer.len() - copied);
+ buffer[copied..copied + chunk_len]
+ .copy_from_slice(&scratch[in_block..in_block + chunk_len]);
+ copied += chunk_len;
+ }
+ _ => return Err(Error::new(EIO)),
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs
index c79266e..86235a4 100644
--- a/src/os/uefi/mod.rs
+++ b/src/os/uefi/mod.rs
@@ -47,17 +47,19 @@ pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 {
let ptr = {
// Max address mapped by src/arch paging code (8 GiB)
let mut ptr = 0x2_0000_0000;
- status_to_result((std::system_table().BootServices.AllocatePages)(
- 1, // AllocateMaxAddress
- MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list
+ if status_to_result((std::system_table().BootServices.AllocatePages)(
+ 0, // AllocateAnyPages
+ MemoryType::EfiLoaderData,
pages,
&mut ptr,
))
- .unwrap();
+ .is_err()
+ {
+ return ptr::null_mut();
+ }
ptr as *mut u8
};
- assert!(!ptr.is_null());
unsafe { ptr::write_bytes(ptr, 0, pages * page_size) };
ptr
}
@@ -1,65 +0,0 @@
# Red Bear OS branding in kernel start messages
# Changes "Redox OS" to "RedBear OS" in architecture start files
# Adds device init logging milestones in x86_shared start path
diff --git a/src/arch/aarch64/start.rs b/src/arch/aarch64/start.rs
index e1c8cfb4..65e3fe33 100644
--- a/src/arch/aarch64/start.rs
+++ b/src/arch/aarch64/start.rs
@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
dtb::serial::init_early(dtb);
}
- info!("Redox OS starting...");
+ info!("RedBear OS starting...");
args.print();
// Initialize RMM
diff --git a/src/arch/riscv64/start.rs b/src/arch/riscv64/start.rs
index 2551968f..a825536a 100644
--- a/src/arch/riscv64/start.rs
+++ b/src/arch/riscv64/start.rs
@@ -97,7 +97,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
init_early(dtb);
}
- info!("Redox OS starting...");
+ info!("RedBear OS starting...");
args.print();
if let Some(dtb) = &dtb {
diff --git a/src/arch/x86_shared/start.rs b/src/arch/x86_shared/start.rs
index 7a7c0ae8..62f9523c 100644
--- a/src/arch/x86_shared/start.rs
+++ b/src/arch/x86_shared/start.rs
@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Set up graphical debug
graphical_debug::init(args.env());
- info!("Redox OS starting...");
+ info!("RedBear OS starting...");
args.print();
// Set up GDT
@@ -127,16 +127,21 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Initialize devices
device::init();
+ info!("kernel: device init complete (PIC + LAPIC)");
// Read ACPI tables, starts APs
if cfg!(feature = "acpi") {
crate::acpi::init(args.acpi_rsdp());
+ info!("kernel: ACPI tables parsed");
device::init_after_acpi();
+ info!("kernel: IOAPIC init complete");
}
crate::profiling::init();
// Initialize all of the non-core devices not otherwise needed to complete initialization
device::init_noncore();
+ info!("kernel: timer init complete, entering userspace");
args.bootstrap()
};
@@ -1,47 +0,0 @@
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -147,6 +147,7 @@ enum ContextHandle {
Priority,
SchedAffinity,
SchedPolicy,
+ Name,
MmapMinAddr(Arc<AddrSpaceWrapper>),
}
@@ -267,6 +268,7 @@ impl ProcScheme {
"sched-affinity" => (ContextHandle::SchedAffinity, true),
// TODO: Switch this kernel-local proc handle over to a stable upstream
// redox_syscall ProcCall::SetSchedPolicy opcode once that lands.
"sched-policy" => (ContextHandle::SchedPolicy, false),
+ "name" => (ContextHandle::Name, false),
"status" => (ContextHandle::Status { privileged: false }, false),
_ if path.starts_with("auth-") => {
let nonprefix = &path["auth-".len()..];
@@ -1218,6 +1220,16 @@ impl ContextHandle {
Ok(2)
}
+ ContextHandle::Name => {
+ let mut name_buf = [0u8; 32];
+ let len = buf.copy_common_bytes_to_slice(&mut name_buf[..31]).unwrap_or(0);
+ let mut context = context.write(token.token());
+ context.name.clear();
+ if let Ok(s) = core::str::from_utf8(&name_buf[..len]) {
+ context.name.push_str(s);
+ }
+ Ok(len)
+ }
ContextHandle::Status { privileged } => {
let mut args = buf.usizes();
@@ -1532,6 +1544,10 @@ impl ContextHandle {
let data = [context.sched_policy as u8, context.sched_rt_priority];
buf.copy_common_bytes_from_slice(&data)
}
+ ContextHandle::Name => {
+ let context = context.read(token.token());
+ buf.copy_common_bytes_from_slice(context.name.as_bytes())
+ }
ContextHandle::Status { .. } => {
let status = {
let context = context.read(token.token());
@@ -1,70 +0,0 @@
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -145,8 +145,9 @@ enum ContextHandle {
// TODO: Remove this once openat is implemented, or allow openat-via-dup via e.g. the top-level
// directory.
OpenViaDup,
+ Priority,
SchedAffinity,
SchedPolicy,
Name,
MmapMinAddr(Arc<AddrSpaceWrapper>),
@@ -160,6 +161,17 @@ pub struct ProcScheme;
static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
static HANDLES: RwLock<L1, HashMap<usize, Handle>> =
RwLock::new(HashMap::with_hasher(DefaultHashBuilder::new()));
+
+const NICE_MIN: i32 = -20;
+const NICE_MAX: i32 = 19;
+
+fn nice_to_kernel_prio(nice: i32) -> usize {
+ (nice.saturating_add(20)).clamp(0, 39) as usize
+}
+
+fn kernel_prio_to_nice(prio: usize) -> i32 {
+ (prio.min(39) as i32) - 20
+}
#[cfg(feature = "debugger")]
#[allow(dead_code)]
pub fn foreach_addrsp(
@@ -253,6 +265,7 @@ impl ProcScheme {
"sighandler" => (ContextHandle::Sighandler, false),
"start" => (ContextHandle::Start, false),
"open_via_dup" => (ContextHandle::OpenViaDup, false),
+ "priority" => (ContextHandle::Priority, false),
"mmap-min-addr" => (
ContextHandle::MmapMinAddr(Arc::clone(
context
@@ -1191,6 +1204,17 @@ impl ContextHandle {
Ok(size_of_val(&mask))
}
+ Self::Priority => {
+ let nice = unsafe { buf.read_exact::<i32>()? };
+ if !(NICE_MIN..=NICE_MAX).contains(&nice) {
+ return Err(Error::new(EINVAL));
+ }
+
+ context
+ .write(token.token())
+ .set_sched_other_prio(nice_to_kernel_prio(nice));
+
+ Ok(size_of::<i32>())
+ }
Self::SchedPolicy => {
if buf.len() != 2 {
return Err(Error::new(EINVAL));
@@ -1522,6 +1546,10 @@ impl ContextHandle {
buf.copy_exactly(crate::cpu_set::mask_as_bytes(&mask))?;
Ok(size_of_val(&mask))
+ }
+ ContextHandle::Priority => {
+ let nice = kernel_prio_to_nice(context.read(token.token()).prio);
+ buf.copy_common_bytes_from_slice(&nice.to_ne_bytes())
}
ContextHandle::SchedPolicy => {
let context = context.read(token.token());
@@ -1,146 +0,0 @@
diff --git a/src/percpu.rs b/src/percpu.rs
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -29,12 +29,14 @@ pub struct PerCpuSched {
pub run_queues_lock: AtomicBool,
pub balance: Cell<[usize; RUN_QUEUE_COUNT]>,
pub last_queue: Cell<usize>,
+ pub last_balance_time: Cell<u128>,
}
impl PerCpuSched {
pub const fn new() -> Self {
const EMPTY: VecDeque<WeakContextRef> = VecDeque::new();
Self {
run_queues: SyncUnsafeCell::new([EMPTY; RUN_QUEUE_COUNT]),
run_queues_lock: AtomicBool::new(false),
balance: Cell::new([0; RUN_QUEUE_COUNT]),
last_queue: Cell::new(0),
+ last_balance_time: Cell::new(0),
}
}
diff --git a/src/context/switch.rs b/src/context/switch.rs
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -33,6 +33,8 @@ const SCHED_PRIO_TO_WEIGHT: [usize; 40] = [
70, 56, 45, 36, 29, 23, 18, 15,
];
+const LOAD_BALANCE_INTERVAL_NS: u128 = 100_000_000;
+
static SCHED_STEAL_COUNT: AtomicUsize = AtomicUsize::new(0);
@@ -101,6 +103,9 @@ pub fn tick(token: &mut CleanLockToken) {
let new_ticks = ticks_cell.get() + 1;
ticks_cell.set(new_ticks);
+ let balance_time = crate::time::monotonic(token);
+ maybe_balance_queues(token, percpu, balance_time);
+
// Trigger a context switch after every 3 ticks.
if new_ticks >= 3 {
switch(token);
@@ -427,6 +432,92 @@ fn steal_work(
None
}
+
+fn queue_depth(percpu: &PercpuBlock) -> usize {
+ let mut sched_lock = SchedQueuesLock::new(&percpu.sched);
+ unsafe {
+ sched_lock
+ .queues_mut()
+ .iter()
+ .map(|queue| queue.len())
+ .sum()
+ }
+}
+
+fn migrate_one_context(
+ token: &mut CleanLockToken,
+ source_id: LogicalCpuId,
+ target_id: LogicalCpuId,
+ switch_time: u128,
+) -> bool {
+ let Some(source) = get_percpu_block(source_id) else {
+ return false;
+ };
+ let Some(target) = get_percpu_block(target_id) else {
+ return false;
+ };
+
+ let source_idle = source.switch_internals.idle_context();
+ let moved = {
+ let mut source_lock = SchedQueuesLock::new(&source.sched);
+ let source_queues = unsafe { source_lock.queues_mut() };
+ pop_movable_context(token, source_queues, target_id, switch_time, &source_idle)
+ };
+
+ let Some((prio, context_ref)) = moved else {
+ return false;
+ };
+
+ let mut target_lock = SchedQueuesLock::new(&target.sched);
+ unsafe {
+ target_lock.queues_mut()[prio].push_back(context_ref);
+ }
+ true
+}
+
+fn maybe_balance_queues(token: &mut CleanLockToken, percpu: &PercpuBlock, balance_time: u128) {
+ if crate::cpu_count() <= 1 || percpu.cpu_id != LogicalCpuId::BSP {
+ return;
+ }
+ if balance_time.saturating_sub(percpu.sched.last_balance_time.get()) < LOAD_BALANCE_INTERVAL_NS
+ {
+ return;
+ }
+
+ percpu.sched.last_balance_time.set(balance_time);
+
+ let mut depths = Vec::new();
+ let mut total_depth = 0usize;
+ for raw_id in 0..crate::cpu_count() {
+ let cpu_id = LogicalCpuId::new(raw_id);
+ let Some(cpu_percpu) = get_percpu_block(cpu_id) else {
+ continue;
+ };
+ let depth = queue_depth(cpu_percpu);
+ total_depth += depth;
+ depths.push((cpu_id, depth));
+ }
+
+ if depths.len() <= 1 || total_depth == 0 {
+ return;
+ }
+
+ let avg_depth = (total_depth + depths.len().saturating_sub(1)) / depths.len();
+
+ for target_index in 0..depths.len() {
+ if depths[target_index].1 != 0 {
+ continue;
+ }
+
+ let mut source_index = None;
+ let mut source_depth = 0usize;
+ for (idx, &(_, depth)) in depths.iter().enumerate() {
+ if idx == target_index {
+ continue;
+ }
+ if depth > avg_depth + 1 && depth > source_depth {
+ source_index = Some(idx);
+ source_depth = depth;
+ }
+ }
+
+ let Some(source_index) = source_index else {
+ continue;
+ };
+
+ let source_id = depths[source_index].0;
+ let target_id = depths[target_index].0;
+ if migrate_one_context(token, source_id, target_id, balance_time) {
+ depths[source_index].1 = depths[source_index].1.saturating_sub(1);
+ depths[target_index].1 += 1;
+ }
+ }
+}
@@ -1,190 +0,0 @@
diff --git a/src/percpu.rs b/src/percpu.rs
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -100,6 +100,14 @@ static ALL_PERCPU_BLOCKS: [AtomicPtr<PercpuBlock>; MAX_CPU_COUNT as usize] =
pub unsafe fn init_tlb_shootdown(id: LogicalCpuId, block: *mut PercpuBlock) {
ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release)
}
+
+pub fn get_percpu_block(id: LogicalCpuId) -> Option<&'static PercpuBlock> {
+ unsafe {
+ ALL_PERCPU_BLOCKS[id.get() as usize]
+ .load(Ordering::Acquire)
+ .as_ref()
+ }
+}
pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> {
diff --git a/src/context/switch.rs b/src/context/switch.rs
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -7,15 +7,15 @@ use crate::{
self, arch, idle_contexts, idle_contexts_try, run_contexts, ArcContextLockWriteGuard,
Context, ContextLock, SchedPolicy, WeakContextRef, RUN_QUEUE_COUNT,
},
- cpu_set::LogicalCpuId,
+ cpu_set::{LogicalCpuId, LogicalCpuSet},
cpu_stats::{self, CpuState},
- percpu::{PerCpuSched, PercpuBlock},
+ percpu::{get_percpu_block, PerCpuSched, PercpuBlock},
sync::{ArcRwLockWriteGuard, CleanLockToken, LockToken, L1, L4},
};
use alloc::{sync::Arc, vec::Vec};
use core::{
cell::{Cell, RefCell},
hint, mem,
- sync::atomic::Ordering,
+ sync::atomic::{AtomicUsize, Ordering},
};
use syscall::PtraceFlags;
@@
+static SCHED_STEAL_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+fn assign_context_to_cpu(context: &mut Context, cpu_id: LogicalCpuId) {
+ context.sched_affinity = LogicalCpuSet::empty();
+ context.sched_affinity.atomic_set(cpu_id);
+}
@@
+fn pop_movable_context(
+ token: &mut CleanLockToken,
+ queues: &mut [alloc::collections::VecDeque<WeakContextRef>; RUN_QUEUE_COUNT],
+ target_cpu: LogicalCpuId,
+ switch_time: u128,
+ idle_context: &Arc<ContextLock>,
+) -> Option<(usize, WeakContextRef)> {
+ for prio in 0..RUN_QUEUE_COUNT {
+ let len = queues[prio].len();
+ for _ in 0..len {
+ let Some(context_ref) = queues[prio].pop_front() else {
+ break;
+ };
+ let Some(context_lock) = context_ref.upgrade() else {
+ continue;
+ };
+ if Arc::ptr_eq(&context_lock, idle_context) {
+ queues[prio].push_back(context_ref);
+ continue;
+ }
+
+ let mut context_guard = unsafe { context_lock.write_arc() };
+ let sw = unsafe { update_stealable(&mut context_guard, switch_time) };
+ if let UpdateResult::CanSwitch = sw {
+ assign_context_to_cpu(&mut context_guard, target_cpu);
+ let moved_ref = WeakContextRef(Arc::downgrade(ArcContextLockWriteGuard::rwlock(
+ &context_guard,
+ )));
+ drop(context_guard);
+ return Some((prio, moved_ref));
+ }
+
+ if matches!(sw, UpdateResult::Blocked) {
+ idle_contexts(token.downgrade()).push_back(context_ref);
+ } else {
+ queues[prio].push_back(context_ref);
+ }
+ }
+ }
+
+ None
+}
+
+fn steal_work(
+ token: &mut CleanLockToken,
+ cpu_id: LogicalCpuId,
+ switch_time: u128,
+) -> Option<ArcContextLockWriteGuard> {
+ let cpu_count = crate::cpu_count();
+ if cpu_count <= 1 {
+ return None;
+ }
+
+ for offset in 1..cpu_count {
+ let victim_id = LogicalCpuId::new((cpu_id.get() + offset) % cpu_count);
+ let Some(victim) = get_percpu_block(victim_id) else {
+ continue;
+ };
+
+ let victim_idle = victim.switch_internals.idle_context();
+ let mut victim_lock = SchedQueuesLock::new(&victim.sched);
+ let victim_queues = unsafe { victim_lock.queues_mut() };
+
+ for prio in 0..RUN_QUEUE_COUNT {
+ let len = victim_queues[prio].len();
+ for _ in 0..len {
+ let Some(context_ref) = victim_queues[prio].pop_front() else {
+ break;
+ };
+ let Some(context_lock) = context_ref.upgrade() else {
+ continue;
+ };
+ if Arc::ptr_eq(&context_lock, &victim_idle) {
+ victim_queues[prio].push_back(context_ref);
+ continue;
+ }
+
+ let mut context_guard = unsafe { context_lock.write_arc() };
+ let sw = unsafe { update_stealable(&mut context_guard, switch_time) };
+ if let UpdateResult::CanSwitch = sw {
+ assign_context_to_cpu(&mut context_guard, cpu_id);
+ SCHED_STEAL_COUNT.fetch_add(1, Ordering::Relaxed);
+ return Some(context_guard);
+ }
+
+ if matches!(sw, UpdateResult::Blocked) {
+ idle_contexts(token.downgrade()).push_back(context_ref);
+ } else {
+ victim_queues[prio].push_back(context_ref);
+ }
+ }
+ }
+ }
+
+ None
+}
+
+unsafe fn update_stealable(context: &mut Context, switch_time: u128) -> UpdateResult {
+ if context.running {
+ return UpdateResult::Skip;
+ }
+ if context.status.is_soft_blocked()
+ && let Some(wake) = context.wake
+ && switch_time >= wake
+ {
+ context.wake = None;
+ context.unblock_no_ipi();
+ }
+ if context.status.is_runnable() {
+ UpdateResult::CanSwitch
+ } else {
+ UpdateResult::Blocked
+ }
+}
@@ -360,6 +469,10 @@ fn wakeup_contexts(token: &mut CleanLockToken, percpu: &PercpuBlock, switch_time
let mut sched_lock = SchedQueuesLock::new(&percpu.sched);
let run_queues = unsafe { sched_lock.queues_mut() };
for (prio, context_ref) in wakeups {
+ if let Some(context_lock) = context_ref.upgrade() {
+ let mut context_guard = unsafe { context_lock.write_arc() };
+ assign_context_to_cpu(&mut context_guard, percpu.cpu_id);
+ }
run_queues[prio].push_back(context_ref);
}
}
@@ -559,6 +672,16 @@ fn select_next_context(
);
return Ok(Some(next_context_guard));
}
+
+ if let Some(next_context_guard) = steal_work(token, cpu_id, switch_time) {
+ queue_previous_context(
+ token,
+ percpu,
+ &prev_context_lock,
+ prev_context_guard,
+ &idle_context,
+ );
+ return Ok(Some(next_context_guard));
+ }
let global_next = {
let contexts_data = run_contexts(token.token());
-147
View File
@@ -1,147 +0,0 @@
diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs
index 3159b9c4..c691eb8d 100644
--- a/src/acpi/madt/mod.rs
+++ b/src/acpi/madt/mod.rs
@@ -146,6 +146,52 @@ pub struct MadtGicd {
_reserved2: [u8; 3],
}
+/// MADT Local x2APIC (entry type 0x9)
+/// Used by modern AMD and Intel platforms with APIC IDs >= 255.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalX2Apic {
+ _reserved: u16,
+ pub x2apic_id: u32,
+ pub flags: u32,
+ pub processor_uid: u32,
+}
+
+/// MADT Local APIC NMI (entry type 0x4)
+/// Configures NMI routing to a processor's LINT0/LINT1 pin.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalApicNmi {
+ pub processor: u8,
+ pub flags: u16,
+ pub nmi_pin: u8,
+}
+
+/// MADT Local APIC Address Override (entry type 0x5)
+/// Provides 64-bit override for the 32-bit local APIC address.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLapicAddressOverride {
+ _reserved: u16,
+ pub local_apic_address: u64,
+}
+
+/// MADT Local x2APIC NMI (entry type 0xA)
+/// x2APIC equivalent of type 0x4 for APIC IDs >= 255.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalX2ApicNmi {
+ _reserved: u16,
+ pub processor_uid: u32,
+ pub flags: u16,
+ pub nmi_pin: u8,
+ _reserved2: u8,
+}
+
+const _: () = assert!(size_of::<MadtLocalApicNmi>() == 4);
+const _: () = assert!(size_of::<MadtLapicAddressOverride>() == 10);
+const _: () = assert!(size_of::<MadtLocalX2ApicNmi>() == 10);
+
/// MADT Entries
#[derive(Debug)]
#[allow(dead_code)]
@@ -160,6 +206,14 @@ pub enum MadtEntry {
InvalidGicc(usize),
Gicd(&'static MadtGicd),
InvalidGicd(usize),
+ LocalX2Apic(&'static MadtLocalX2Apic),
+ InvalidLocalX2Apic(usize),
+ LocalApicNmi(&'static MadtLocalApicNmi),
+ InvalidLocalApicNmi(usize),
+ LapicAddressOverride(&'static MadtLapicAddressOverride),
+ InvalidLapicAddressOverride(usize),
+ LocalX2ApicNmi(&'static MadtLocalX2ApicNmi),
+ InvalidLocalX2ApicNmi(usize),
Unknown(u8),
}
@@ -176,6 +230,10 @@ impl Iterator for MadtIter {
let entry_len =
unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize;
+ if entry_len < 2 {
+ return None;
+ }
+
if self.i + entry_len <= self.sdt.data_len() {
let item = match entry_type {
0x0 => {
@@ -224,6 +282,44 @@ impl Iterator for MadtIter {
MadtEntry::InvalidGicd(entry_len)
}
}
+ 0x9 => {
+ if entry_len == size_of::<MadtLocalX2Apic>() + 2 {
+ MadtEntry::LocalX2Apic(unsafe {
+ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalX2Apic)
+ })
+ } else {
+ MadtEntry::InvalidLocalX2Apic(entry_len)
+ }
+ }
+ 0x4 => {
+ if entry_len == size_of::<MadtLocalApicNmi>() + 2 {
+ MadtEntry::LocalApicNmi(unsafe {
+ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApicNmi)
+ })
+ } else {
+ MadtEntry::InvalidLocalApicNmi(entry_len)
+ }
+ }
+ 0x5 => {
+ if entry_len == size_of::<MadtLapicAddressOverride>() + 2 {
+ MadtEntry::LapicAddressOverride(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLapicAddressOverride)
+ })
+ } else {
+ MadtEntry::InvalidLapicAddressOverride(entry_len)
+ }
+ }
+ 0xA => {
+ if entry_len == size_of::<MadtLocalX2ApicNmi>() + 2 {
+ MadtEntry::LocalX2ApicNmi(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLocalX2ApicNmi)
+ })
+ } else {
+ MadtEntry::InvalidLocalX2ApicNmi(entry_len)
+ }
+ }
_ => MadtEntry::Unknown(entry_type),
};
diff --git a/src/devices/graphical_debug/mod.rs b/src/devices/graphical_debug/mod.rs
index b701c9a8..00cc984d 100644
--- a/src/devices/graphical_debug/mod.rs
+++ b/src/devices/graphical_debug/mod.rs
@@ -59,7 +59,12 @@ pub fn init(env: &[u8]) {
);
let debug_display = DebugDisplay::new(width, height, stride, virt as *mut u32);
- *DEBUG_DISPLAY.lock() = Some(debug_display);
+ // FIXME: Writing to the framebuffer during early boot causes a hang on some
+ // QEMU configurations (virtio-vga, ramfb). The bootloader maps the framebuffer
+ // with default caching; the kernel remaps it with write-combining in memory::init().
+ // Early kernel access before that remap appears to stall. Deferring DEBUG_DISPLAY
+ // setup avoids the hang; userspace vesad/fbbootlogd handles graphical output.
+ // *DEBUG_DISPLAY.lock() = Some(debug_display);
}
#[allow(unused)]
-102
View File
@@ -1,102 +0,0 @@
--- a/b/src/connection.c 2025-07-06 13:11:26.000000000 +0100
+++ b/src/connection.c 2026-05-01 00:15:42.778777823 +0100
@@ -40,6 +40,12 @@
#include <time.h>
#include <ffi.h>
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+extern FILE *open_memstream(char **bufp, size_t *sizep);
+
#include "wayland-util.h"
#include "wayland-private.h"
#include "wayland-os.h"
--- a/b/src/event-loop.c 2025-07-06 13:11:26.000000000 +0100
+++ b/src/event-loop.c 2026-05-01 00:15:42.778845239 +0100
@@ -35,9 +35,43 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
-#include <sys/signalfd.h>
-#include <sys/timerfd.h>
#include <unistd.h>
+/* Redox: relibc declares signalfd/timerfd in headers but has no implementation.
+ Provide inline implementations via Redox schemes. */
+#define SFD_CLOEXEC O_CLOEXEC
+#define SFD_NONBLOCK O_NONBLOCK
+#define TFD_CLOEXEC O_CLOEXEC
+#define TFD_NONBLOCK O_NONBLOCK
+#define TFD_TIMER_ABSTIME 0x1
+struct signalfd_siginfo { uint8_t pad[128]; };
+static int signalfd(int fd, const sigset_t *mask, int flags) {
+ int oflag = O_RDWR;
+ if (flags & SFD_CLOEXEC) oflag |= O_CLOEXEC;
+ if (flags & SFD_NONBLOCK) oflag |= O_NONBLOCK;
+ if (fd == -1) { fd = open("/scheme/event", oflag); if (fd < 0) return -1; }
+ else { if (flags & SFD_CLOEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); }
+ sigprocmask(SIG_BLOCK, mask, NULL);
+ return fd;
+}
+static int timerfd_create(int clockid, int flags) {
+ int oflag = O_RDWR;
+ if (flags & TFD_CLOEXEC) oflag |= O_CLOEXEC;
+ if (flags & TFD_NONBLOCK) oflag |= O_NONBLOCK;
+ char path[64];
+ snprintf(path, sizeof(path), "/scheme/time/%d", clockid);
+ return open(path, oflag);
+}
+static int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) {
+ if (new_value == NULL) { errno = EFAULT; return -1; }
+ ssize_t r = write(fd, &new_value->it_value, sizeof(struct timespec));
+ return (r == sizeof(struct timespec)) ? 0 : -1;
+}
+static int timerfd_gettime(int fd, struct itimerspec *curr) {
+ if (curr == NULL) { errno = EFAULT; return -1; }
+ curr->it_interval = (struct timespec){0};
+ ssize_t r = read(fd, &curr->it_value, sizeof(struct timespec));
+ return (r == sizeof(struct timespec)) ? 0 : -1;
+}
#include "timespec-util.h"
#include "wayland-util.h"
#include "wayland-private.h"
--- a/b/src/meson.build 2025-07-06 13:11:26.000000000 +0100
+++ b/src/meson.build 2026-05-01 00:15:42.778925799 +0100
@@ -81,8 +81,7 @@
endif
if meson.is_cross_build() or not get_option('scanner')
- scanner_dep = dependency('wayland-scanner', native: true, version: meson.project_version())
- wayland_scanner_for_build = find_program(scanner_dep.get_variable(pkgconfig: 'wayland_scanner'))
+ wayland_scanner_for_build = find_program('wayland-scanner', native: true)
else
wayland_scanner_for_build = wayland_scanner
endif
--- a/b/src/wayland-server.c 2025-07-06 13:11:26.000000000 +0100
+++ b/src/wayland-server.c 2026-05-01 00:15:42.779083803 +0100
@@ -39,7 +39,23 @@
#include <dlfcn.h>
#include <sys/time.h>
#include <fcntl.h>
-#include <sys/eventfd.h>
+#ifndef EFD_CLOEXEC
+#define EFD_CLOEXEC O_CLOEXEC
+#endif
+#ifndef EFD_NONBLOCK
+#define EFD_NONBLOCK O_NONBLOCK
+#endif
+#ifndef EFD_SEMAPHORE
+#define EFD_SEMAPHORE 0x1
+#endif
+static int eventfd(unsigned int initval, int flags) {
+ int oflag = O_RDWR;
+ if (flags & EFD_CLOEXEC) oflag |= O_CLOEXEC;
+ if (flags & EFD_NONBLOCK) oflag |= O_NONBLOCK;
+ char path[64];
+ snprintf(path, sizeof(path), "/scheme/event/eventfd/%u/%d", initval, (flags & EFD_SEMAPHORE) ? 1 : 0);
+ return open(path, oflag);
+}
#include <sys/file.h>
#include <sys/stat.h>
@@ -1,27 +0,0 @@
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp
index e857f1a..f4f7f4a 100644
--- a/src/corelib/io/qfilesystemengine_unix.cpp
+++ b/src/corelib/io/qfilesystemengine_unix.cpp
@@ -27,23 +27,6 @@
#include <stdio.h>
#include <errno.h>
-#ifdef Q_OS_REDOX
-// relibc does not provide unlinkat/linkat yet (POSIX.1-2008 *at functions).
-// Provide inline stubs that work for AT_FDCWD only - sufficient for
-// FreeDesktop trash operations in this file.
-#include <fcntl.h>
-static inline int unlinkat(int dirfd, const char *pathname, int flags)
-{
- if (dirfd != AT_FDCWD || flags != 0) { errno = ENOTSUP; return -1; }
- return unlink(pathname);
-}
-static inline int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags)
-{
- if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD || flags != 0) { errno = ENOTSUP; return -1; }
- return link(oldpath, newpath);
-}
-#endif
-
#include <chrono>
#include <memory> // for std::unique_ptr
@@ -1,56 +0,0 @@
--- qtb-orig/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h 2026-03-05 07:51:49.000000000 +0000
+++ qtb-mod/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h 2026-04-28 14:03:29.046092114 +0100
@@ -51,9 +51,15 @@
virtual bool supportsThreadedOpenGL() const { return false; }
virtual bool supportsWindowDecoration() const { return false; }
+ #if QT_CONFIG(opengl)
+#if QT_CONFIG(opengl)
+#if QT_CONFIG(opengl)
virtual QWaylandWindow *createEglWindow(QWindow *window) = 0;
virtual QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const = 0;
+#endif
+#endif
virtual bool canCreatePlatformOffscreenSurface() const { return false; }
+#endif
#if QT_CONFIG(opengl)
virtual QOpenGLContext *createOpenGLContext(EGLContext context, EGLDisplay contextDisplay, QOpenGLContext *shareContext) const = 0;
virtual QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const { Q_UNUSED(surface); return nullptr; }
@@ -65,7 +71,11 @@
EglContext
};
virtual void *nativeResource(NativeResource /*resource*/) { return nullptr; }
+#if QT_CONFIG(opengl)
+#if QT_CONFIG(opengl)
virtual void *nativeResourceForContext(NativeResource /*resource*/, QPlatformOpenGLContext */*context*/) { return nullptr; }
+#endif
+#endif
};
}
--- qtb-orig/src/plugins/platforms/wayland/qwaylandintegration.cpp 2026-03-05 07:51:49.000000000 +0000
+++ qtb-mod/src/plugins/platforms/wayland/qwaylandintegration.cpp 2026-04-28 14:34:26.740450805 +0100
@@ -135,17 +135,23 @@
case ScreenWindowGrabbing: // whether QScreen::grabWindow() is supported
return false;
case OffscreenSurface:
+#if QT_CONFIG(opengl)
return mDisplay->clientBufferIntegration()
&& mDisplay->clientBufferIntegration()->canCreatePlatformOffscreenSurface();
+#else
+ return false;
+#endif
default: return QPlatformIntegration::hasCapability(cap);
}
}
QPlatformWindow *QWaylandIntegration::createPlatformWindow(QWindow *window) const
{
+#if QT_CONFIG(opengl)
if (window->surfaceType() == QWindow::OpenGLSurface
&& mDisplay->clientBufferIntegration())
return mDisplay->clientBufferIntegration()->createEglWindow(window);
+#endif
#if QT_CONFIG(vulkan)
if (window->surfaceType() == QSurface::VulkanSurface)
@@ -1,23 +0,0 @@
diff -ruwN source-old/src/CMakeLists.txt source/src/CMakeLists.txt
--- source-old/src/CMakeLists.txt 2024-12-02 05:39:06.000000000 +0000
+++ source/src/CMakeLists.txt 2026-04-30 00:00:00.000000000 +0000
@@ -54,5 +54,5 @@
endif()
if (QT_FEATURE_network)
- # add_subdirectory(network) # disabled for Redox
+ add_subdirectory(network)
if (ANDROID)
add_subdirectory(network/android/jar)
endif()
diff -ruwN source-old/src/plugins/generic/CMakeLists.txt source/src/plugins/generic/CMakeLists.txt
--- source-old/src/plugins/generic/CMakeLists.txt 2024-12-02 05:39:06.000000000 +0000
+++ source/src/plugins/generic/CMakeLists.txt 2026-04-30 00:00:00.000000000 +0000
@@ -18,6 +18,6 @@
add_subdirectory(tslib)
endif()
if(QT_FEATURE_tuiotouch)
- # add_subdirectory(tuiotouch) # disabled for Redox (needs Network)
+ add_subdirectory(tuiotouch)
endif()
if(QT_FEATURE_libinput)
add_subdirectory(libinput)
@@ -1,722 +0,0 @@
diff --git a/src/header/netdb/dns/mod.rs b/src/header/netdb/dns/mod.rs
index 9d7e44b..f5bc21b 100644
--- a/src/header/netdb/dns/mod.rs
+++ b/src/header/netdb/dns/mod.rs
@@ -15,6 +15,35 @@ use alloc::{string::String, vec::Vec};
mod answer;
mod query;
+const DNS_FLAG_QR: u16 = 0x8000;
+const DNS_FLAG_TC: u16 = 0x0200;
+const DNS_RCODE_MASK: u16 = 0x000F;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(super) enum DnsError {
+ MalformedResponse,
+ TransactionIdMismatch,
+ NotResponse,
+ Truncated,
+ ServerFailure,
+ NameError,
+ ResponseCode(u8),
+}
+
+impl DnsError {
+ fn as_str(self) -> &'static str {
+ match self {
+ Self::MalformedResponse => "malformed dns response",
+ Self::TransactionIdMismatch => "dns transaction id mismatch",
+ Self::NotResponse => "dns packet was not a response",
+ Self::Truncated => "truncated dns response",
+ Self::ServerFailure => "dns server failure",
+ Self::NameError => "dns name error",
+ Self::ResponseCode(_) => "dns server returned an error response",
+ }
+ }
+}
#[derive(Clone, Debug)]
pub struct Dns {
pub transaction_id: u16,
@@ -59,6 +88,14 @@ impl Dns {
}
pub fn parse(data: &[u8]) -> Result<Self, String> {
+ Self::parse_impl(data, None).map_err(|err| err.as_str().into())
+ }
+
+ pub(super) fn parse_reply(data: &[u8], expected_transaction_id: u16) -> Result<Self, DnsError> {
+ Self::parse_impl(data, Some(expected_transaction_id))
+ }
+
+ fn parse_impl(data: &[u8], expected_transaction_id: Option<u16>) -> Result<Self, DnsError> {
let name_ind = 0b1100_0000;
let mut i = 0;
@@ -66,7 +103,7 @@ impl Dns {
() => {{
i += 1;
if i > data.len() {
- return Err(format!("{}: {}: pop_u8", file!(), line!()));
+ return Err(DnsError::MalformedResponse);
}
data[i - 1]
}};
@@ -77,9 +114,11 @@ impl Dns {
use core::convert::TryInto;
i += 2;
if i > data.len() {
- return Err(format!("{}: {}: pop_n16", file!(), line!()));
+ return Err(DnsError::MalformedResponse);
}
- let bytes: [u8; 2] = data[i - 2..i].try_into().unwrap();
+ let bytes: [u8; 2] = data[i - 2..i]
+ .try_into()
+ .map_err(|_| DnsError::MalformedResponse)?;
u16::from_be_bytes(bytes)
}};
}
@@ -156,11 +195,83 @@ impl Dns {
});
}
- Ok(Dns {
+ let dns = Dns {
transaction_id,
flags,
queries,
answers,
- })
+ };
+
+ if let Some(expected_transaction_id) = expected_transaction_id {
+ if dns.transaction_id != expected_transaction_id {
+ return Err(DnsError::TransactionIdMismatch);
+ }
+ }
+
+ if dns.flags & DNS_FLAG_QR == 0 {
+ return Err(DnsError::NotResponse);
+ }
+
+ if dns.flags & DNS_FLAG_TC != 0 {
+ return Err(DnsError::Truncated);
+ }
+
+ match (dns.flags & DNS_RCODE_MASK) as u8 {
+ 0 => Ok(dns),
+ 2 => Err(DnsError::ServerFailure),
+ 3 => Err(DnsError::NameError),
+ rcode => Err(DnsError::ResponseCode(rcode)),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use alloc::{string::ToString, vec::Vec};
+
+ use super::{Dns, DnsError, DnsQuery};
+
+ fn packet(transaction_id: u16, flags: u16) -> Vec<u8> {
+ Dns {
+ transaction_id,
+ flags,
+ queries: vec![DnsQuery {
+ name: "example.com".to_string(),
+ q_type: 0x0001,
+ q_class: 0x0001,
+ }],
+ answers: vec![],
+ }
+ .compile()
+ }
+
+ #[test]
+ fn parse_reply_accepts_valid_response() {
+ let response = Dns::parse_reply(&packet(0x1234, 0x8180), 0x1234).unwrap();
+ assert_eq!(response.transaction_id, 0x1234);
+ }
+
+ #[test]
+ fn parse_reply_rejects_transaction_id_mismatch() {
+ let err = Dns::parse_reply(&packet(0x1234, 0x8180), 0x4321).unwrap_err();
+ assert_eq!(err, DnsError::TransactionIdMismatch);
+ }
+
+ #[test]
+ fn parse_rejects_query_packets() {
+ let err = Dns::parse(&packet(0x1234, 0x0100)).unwrap_err();
+ assert_eq!(err, DnsError::NotResponse.as_str());
+ }
+
+ #[test]
+ fn parse_rejects_truncated_response() {
+ let err = Dns::parse(&packet(0x1234, 0x8380)).unwrap_err();
+ assert_eq!(err, DnsError::Truncated.as_str());
+ }
+
+ #[test]
+ fn parse_rejects_name_error_response() {
+ let err = Dns::parse(&packet(0x1234, 0x8183)).unwrap_err();
+ assert_eq!(err, DnsError::NameError.as_str());
+ }
}
diff --git a/src/header/netdb/lookup.rs b/src/header/netdb/lookup.rs
index c2b6cdb..af25f97 100644
--- a/src/header/netdb/lookup.rs
+++ b/src/header/netdb/lookup.rs
@@ -1,10 +1,10 @@
-use alloc::{boxed::Box, string::ToString, vec::Vec};
+use alloc::{string::ToString, vec::Vec};
use core::{mem, ptr};
use crate::{
out::Out,
platform::{
- Pal, Sys,
+ self, Pal, Sys,
types::{c_int, c_void},
},
};
@@ -25,13 +25,86 @@ use crate::header::{
};
use super::{
- dns::{Dns, DnsQuery},
+ dns::{Dns, DnsError, DnsQuery},
sys::get_dns_server,
};
pub type LookupHost = Vec<in_addr>;
pub type LookupHostV6 = Vec<in6_addr>;
+
+fn close_socket(sock: c_int) {
+ if sock >= 0 {
+ if let Ok(()) = Sys::close(sock) {};
+ }
+}
+
+fn last_socket_error(default: c_int) -> c_int {
+ match platform::ERRNO.get() {
+ 0 => default,
+ err => err,
+ }
+}
+
+fn map_dns_error(err: DnsError) -> c_int {
+ match err {
+ DnsError::NameError => ENOENT,
+ DnsError::ServerFailure => EAGAIN,
+ DnsError::Truncated => EMSGSIZE,
+ DnsError::MalformedResponse
+ | DnsError::TransactionIdMismatch
+ | DnsError::NotResponse
+ | DnsError::ResponseCode(_) => EREMOTEIO,
+ }
+}
+
+fn lookup_dns_response(packet: &Dns, dns_addr: u32) -> Result<Dns, c_int> {
+ let packet_data = packet.compile();
+ let packet_data_len = packet_data.len();
+ let packet_data_ptr = packet_data.as_ptr().cast::<c_void>();
+
+ let dest = sockaddr_in {
+ sin_family: AF_INET as u16,
+ sin_port: htons(53),
+ sin_addr: in_addr { s_addr: dns_addr },
+ ..Default::default()
+ };
+ let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
+
+ let sock = unsafe { sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)) };
+ if sock < 0 {
+ return Err(last_socket_error(EIO));
+ }
+
+ if unsafe { sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) } < 0 {
+ let err = last_socket_error(EIO);
+ close_socket(sock);
+ return Err(err);
+ }
+
+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
+ let err = last_socket_error(EIO);
+ close_socket(sock);
+ return Err(err);
+ }
+
+ let tv = timeval {
+ tv_sec: 5,
+ tv_usec: 0,
+ };
+ unsafe {
+ sys_socket::setsockopt(
+ sock,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ &tv as *const timeval as *const c_void,
+ core::mem::size_of::<timeval>() as socklen_t,
+ );
+ }
+
+ let mut buf = vec![0u8; 65536];
+ let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
+
+ let mut count: isize = -1;
+ let mut recv_error = EIO;
+ for attempt in 0..2 {
+ count = unsafe { sys_socket::recv(sock, buf_ptr, buf.len(), 0) };
+ if count >= 0 {
+ break;
+ }
+
+ recv_error = last_socket_error(EIO);
+ if attempt + 1 == 2 {
+ break;
+ }
+
+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
+ recv_error = last_socket_error(EIO);
+ break;
+ }
+ }
+
+ if count < 0 {
+ close_socket(sock);
+ return Err(recv_error);
+ }
+
+ let response = match Dns::parse_reply(&buf[..count as usize], packet.transaction_id) {
+ Ok(response) => response,
+ Err(err) => {
+ close_socket(sock);
+ return Err(map_dns_error(err));
+ }
+ };
+
+ close_socket(sock);
+ Ok(response)
+}
pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
if let Some(host_direct_addr) = parse_ipv4_string(host) {
@@ -61,97 +134,30 @@ pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
-
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- drop(Box::from_raw(packet_data_ptr));
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = vec![0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let addrs: Vec<_> = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x0001
- && answer.a_class == 0x0001
- && answer.data.len() == 4
- {
- let addr = in_addr {
- s_addr: u32::from_ne_bytes([
- answer.data[0],
- answer.data[1],
- answer.data[2],
- answer.data[3],
- ]),
- };
- Some(addr)
- } else {
- None
- }
- })
- .collect();
-
- Ok(addrs)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let addrs: Vec<_> = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x0001 && answer.a_class == 0x0001 && answer.data.len() == 4 {
+ let addr = in_addr {
+ s_addr: u32::from_ne_bytes([
+ answer.data[0],
+ answer.data[1],
+ answer.data[2],
+ answer.data[3],
+ ]),
+ };
+ Some(addr)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(addrs)
} else {
Err(EINVAL)
}
@@ -186,97 +192,30 @@ pub fn lookup_host_v6(host: &str) -> Result<LookupHostV6, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
-
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- drop(Box::from_raw(packet_data_ptr));
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = vec![0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let addrs: Vec<_> = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x001c
- && answer.a_class == 0x0001
- && answer.data.len() == 16
- {
- let mut s6_addr = [0u8; 16];
- s6_addr.copy_from_slice(&answer.data[..16]);
- Some(in6_addr { s6_addr })
- } else {
- None
- }
- })
- .collect();
-
- Ok(addrs)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let addrs: Vec<_> = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x001c && answer.a_class == 0x0001 && answer.data.len() == 16 {
+ let mut s6_addr = [0u8; 16];
+ s6_addr.copy_from_slice(&answer.data[..16]);
+ Some(in6_addr { s6_addr })
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(addrs)
} else {
Err(EINVAL)
}
@@ -315,82 +254,24 @@ pub fn lookup_addr(addr: in_addr) -> Result<Vec<Vec<u8>>, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
-
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- return Err(EIO);
- }
- }
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = [0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let names = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x000C && answer.a_class == 0x0001 {
- // answer.data is encoded kinda weird.
- // Basically length-prefixed strings for each
- // subsection of the domain.
- // We need to parse this to insert periods where
- // they belong (ie at the end of each string)
- Some(parse_revdns_answer(&answer.data))
- } else {
- None
- }
- })
- .collect();
- Ok(names)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let names = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x000C && answer.a_class == 0x0001 {
+ // answer.data is encoded kinda weird.
+ // Basically length-prefixed strings for each
+ // subsection of the domain.
+ // We need to parse this to insert periods where
+ // they belong (ie at the end of each string)
+ Some(parse_revdns_answer(&answer.data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ Ok(names)
} else {
Err(EINVAL)
}
diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs
index ba58b6e..cdcc10e 100644
--- a/src/header/netdb/mod.rs
+++ b/src/header/netdb/mod.rs
@@ -180,6 +180,31 @@ fn bytes_to_box_str(bytes: &[u8]) -> Box<str> {
Box::from(core::str::from_utf8(bytes).unwrap_or(""))
}
+fn lookup_error_to_eai(err: c_int) -> c_int {
+ match err {
+ ETIMEDOUT | EAGAIN => EAI_AGAIN,
+ ENOENT => EAI_NONAME,
+ _ => EAI_FAIL,
+ }
+}
+
+fn lookup_error_priority(err: c_int) -> u8 {
+ match err {
+ EAI_AGAIN => 3,
+ EAI_FAIL => 2,
+ EAI_NONAME => 1,
+ _ => 0,
+ }
+}
+
+fn combine_lookup_error(current: Option<c_int>, err: c_int) -> c_int {
+ let mapped = lookup_error_to_eai(err);
+
+ match current {
+ Some(existing) if lookup_error_priority(existing) >= lookup_error_priority(mapped) => {
+ existing
+ }
+ Some(_) => mapped,
+ None => mapped,
+ }
+}
+
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endnetent.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn endnetent() {
@@ -926,6 +951,8 @@ pub unsafe extern "C" fn getaddrinfo(
let want_inet4 = requested_family == AF_INET || requested_family == AF_UNSPEC;
let want_inet6 = requested_family == AF_INET6 || requested_family == AF_UNSPEC;
+ let mut lookup_error = None;
+
let lookuphost_v4: Vec<in_addr> = if want_inet4 {
if ai_flags & AI_NUMERICHOST > 0 {
match parse_ipv4_string(node_str) {
@@ -937,7 +964,10 @@ pub unsafe extern "C" fn getaddrinfo(
} else {
match lookup_host(node_str) {
Ok(addrs) => addrs,
- Err(_) => vec![],
+ Err(err) => {
+ lookup_error = Some(combine_lookup_error(lookup_error, err));
+ vec![]
+ }
}
}
} else {
@@ -955,7 +985,10 @@ pub unsafe extern "C" fn getaddrinfo(
} else {
match lookup_host_v6(node_str) {
Ok(addrs) => addrs,
- Err(_) => vec![],
+ Err(err) => {
+ lookup_error = Some(combine_lookup_error(lookup_error, err));
+ vec![]
+ }
}
}
} else {
@@ -963,7 +996,7 @@ pub unsafe extern "C" fn getaddrinfo(
};
if lookuphost_v4.is_empty() && lookuphost_v6.is_empty() {
- return EAI_NONAME;
+ return lookup_error.unwrap_or(EAI_NONAME);
}
@@ -1,15 +0,0 @@
diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk
--- a/tests/Makefile.tests.mk
+++ b/tests/Makefile.tests.mk
@@ -314,8 +314,12 @@ VARIED_NAMES=\
grp/gr_iter \
semaphore/named \
semaphore/unnamed \
+ sys_eventfd/eventfd \
+ sys_signalfd/header_only \
+ sys_signalfd/signalfd \
+ sys_timerfd/timerfd \
waitid \
waitpid \
waitpid_multiple \
$(FAILING_TESTS)
-230
View File
@@ -1,230 +0,0 @@
diff --git a/src/header/_fenv/mod.rs b/src/header/_fenv/mod.rs
--- a/src/header/_fenv/mod.rs
+++ b/src/header/_fenv/mod.rs
@@ -4,82 +4,207 @@
use crate::platform::types::c_int;
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
-pub const FE_ALL_EXCEPT: c_int = 0;
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
-pub const FE_TONEAREST: c_int = 0;
+// x86_64 SSE floating-point exception flags (MXCSR bits 0-5, excluding denormal bit 1)
+pub const FE_INVALID: c_int = 0x01;
+pub const FE_DIVBYZERO: c_int = 0x04;
+pub const FE_OVERFLOW: c_int = 0x08;
+pub const FE_UNDERFLOW: c_int = 0x10;
+pub const FE_INEXACT: c_int = 0x20;
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
+pub const FE_ALL_EXCEPT: c_int =
+ FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT;
+
+// x86_64 rounding modes (MXCSR bits 13-14, x87 CW bits 10-11)
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
+pub const FE_TONEAREST: c_int = 0x000;
+pub const FE_DOWNWARD: c_int = 0x400;
+pub const FE_UPWARD: c_int = 0x800;
+pub const FE_TOWARDZERO: c_int = 0xC00;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
pub type fexcept_t = u64;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
#[repr(C)]
pub struct fenv_t {
- pub cw: u64,
+ pub cw: u32, // x87 control word (zero-extended from u16)
+ pub mxcsr: u32, // SSE MXCSR register
}
+/// Read the x87 FPU control word.
+#[inline]
+unsafe fn fnstcw() -> u16 {
+ let mut cw: u16 = 0;
+ core::arch::asm!(
+ "fnstcw ({0})",
+ in(reg) &mut cw,
+ options(nostack, preserves_flags)
+ );
+ cw
+}
+
+/// Load the x87 FPU control word.
+#[inline]
+unsafe fn fldcw(cw: u16) {
+ core::arch::asm!(
+ "fldcw ({0})",
+ in(reg) &cw,
+ options(nostack, preserves_flags)
+ );
+}
+
+/// Read the SSE MXCSR register.
+#[inline]
+unsafe fn stmxcsr() -> u32 {
+ let mut mxcsr: u32 = 0;
+ core::arch::asm!(
+ "stmxcsr ({0})",
+ in(reg) &mut mxcsr,
+ options(nostack, preserves_flags)
+ );
+ mxcsr
+}
+
+/// Write the SSE MXCSR register.
+#[inline]
+unsafe fn ldmxcsr(val: u32) {
+ core::arch::asm!(
+ "ldmxcsr ({0})",
+ in(reg) &val,
+ options(nostack, preserves_flags)
+ );
+}
+
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feclearexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int {
- unimplemented!();
+ let mask = (excepts & FE_ALL_EXCEPT) as u32;
+ if mask != 0 {
+ let mxcsr = stmxcsr();
+ ldmxcsr(mxcsr & !mask);
+ // Clear x87 status word exception flags
+ core::arch::asm!("fnclex", options(nostack, preserves_flags));
+ }
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int {
- unimplemented!();
+ if envp.is_null() {
+ return 1;
+ }
+ (*envp).cw = fnstcw() as u32;
+ (*envp).mxcsr = stmxcsr();
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetexceptflag.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetexceptflag(flagp: *mut fexcept_t, excepts: c_int) -> c_int {
- unimplemented!();
+ if flagp.is_null() {
+ return 1;
+ }
+ let mxcsr = stmxcsr();
+ *flagp = (mxcsr & FE_ALL_EXCEPT as u32 & excepts as u32) as fexcept_t;
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetround.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetround() -> c_int {
- FE_TONEAREST
+ let mxcsr = stmxcsr();
+ (mxcsr & 0xC00) as c_int
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feholdexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feholdexcept(envp: *mut fenv_t) -> c_int {
- unimplemented!();
+ if envp.is_null() {
+ return 1;
+ }
+ // Save current environment
+ (*envp).cw = fnstcw() as u32;
+ (*envp).mxcsr = stmxcsr();
+ // Clear all exception flags and set non-stop mode (unmask all exceptions)
+ // MXCSR: clear status bits 0-5, clear mask bits 7-12
+ let mxcsr = stmxcsr();
+ ldmxcsr(mxcsr & !(FE_ALL_EXCEPT as u32) & !((FE_ALL_EXCEPT as u32) << 7));
+ // x87: clear exception mask bits (bits 0-5 in CW) and clear status
+ let cw = fnstcw();
+ fldcw(cw & !0x3F);
+ core::arch::asm!("fnclex", options(nostack, preserves_flags));
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feraiseexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int {
- unimplemented!();
+ let mask = (excepts & FE_ALL_EXCEPT) as u32;
+ if mask == 0 {
+ return 0;
+ }
+ // Set exception status flags in MXCSR
+ let mxcsr = stmxcsr();
+ ldmxcsr(mxcsr | mask);
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fesetenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int {
- unimplemented!();
+ if envp.is_null() {
+ // Restore default environment
+ fldcw(0x037F); // x87 default CW: all exceptions masked, double precision
+ ldmxcsr(0x1F80); // MXCSR default: all exceptions masked, round-to-nearest
+ return 0;
+ }
+ fldcw((*envp).cw as u16);
+ ldmxcsr((*envp).mxcsr);
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fesetexceptflag.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetexceptflag(flagp: *const fexcept_t, excepts: c_int) -> c_int {
- unimplemented!();
+ if flagp.is_null() {
+ return 1;
+ }
+ let mask = (excepts & FE_ALL_EXCEPT) as u32;
+ let mxcsr = stmxcsr();
+ let flags = (*flagp as u32) & mask;
+ ldmxcsr((mxcsr & !mask) | flags);
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetround.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetround(round: c_int) -> c_int {
- unimplemented!();
+ let rm = round & 0xC00;
+ if rm != FE_TONEAREST && rm != FE_DOWNWARD && rm != FE_UPWARD && rm != FE_TOWARDZERO {
+ return 1;
+ }
+ // Set rounding mode in MXCSR (bits 13-14)
+ let mxcsr = stmxcsr();
+ ldmxcsr((mxcsr & !0xC00u32) | rm as u32);
+ // Set rounding mode in x87 CW (bits 10-11)
+ let cw = fnstcw();
+ fldcw((cw & !0x0C00) | rm as u16);
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fetestexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int {
- unimplemented!();
+ let mxcsr = stmxcsr();
+ (mxcsr & FE_ALL_EXCEPT as u32 & excepts as u32) as c_int
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feupdateenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feupdateenv(envp: *const fenv_t) -> c_int {
- unimplemented!();
+ let mxcsr = stmxcsr();
+ let excepts = (mxcsr & FE_ALL_EXCEPT as u32) as c_int;
+ if fesetenv(envp) != 0 {
+ return 1;
+ }
+ feraiseexcept(excepts);
+ 0
}
@@ -1,182 +0,0 @@
--- a/src/header/semaphore/mod.rs 2026-04-25 17:07:53.742796721 +0100
+++ b/src/header/semaphore/mod.rs 2026-04-25 17:08:54.527084219 +0100
@@ -2,12 +2,24 @@
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
+use core::mem::size_of;
+
use crate::{
+ c_str::CStr,
header::{
bits_timespec::timespec,
+ errno::{EEXIST, EINVAL},
+ fcntl::{O_CREAT, O_EXCL, O_RDWR},
+ sys_mman::{
+ mmap, munmap, shm_open, shm_unlink, MAP_SHARED, MAP_FAILED, PROT_READ, PROT_WRITE,
+ },
time::{CLOCK_MONOTONIC, CLOCK_REALTIME},
+ unistd::{close, ftruncate},
+ },
+ platform::{
+ ERRNO,
+ types::{c_char, c_int, c_long, c_uint, clockid_t, c_void, mode_t, off_t, size_t},
},
- platform::types::{c_char, c_int, c_long, c_uint, clockid_t},
};
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
@@ -18,12 +30,17 @@
pub size: [c_char; 4],
pub align: c_long,
}
+
+/// Pointer value returned by `sem_open` on failure.
+/// cbindgen:ignore
+pub const SEM_FAILED: *mut sem_t = usize::MAX as *mut sem_t;
+
pub type RlctSempahore = crate::sync::Semaphore;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_close.html>.
-// #[unsafe(no_mangle)]
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_close(sem: *mut sem_t) -> c_int {
- todo!("named semaphores")
+ unsafe { munmap(sem.cast::<c_void>(), size_of::<sem_t>()) }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_destroy.html>.
@@ -50,13 +67,105 @@
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_open.html>.
-// TODO: va_list
-// #[unsafe(no_mangle)]
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_open(
name: *const c_char,
- oflag: c_int, /* (va_list) value: c_uint */
+ oflag: c_int,
+ mut __valist: ...
) -> *mut sem_t {
- todo!("named semaphores")
+ // Validate name: must start with '/', no embedded '/'.
+ if name.is_null() {
+ ERRNO.set(EINVAL);
+ return SEM_FAILED;
+ }
+
+ let name_c = unsafe { CStr::from_ptr(name) };
+ let name_bytes = name_c.to_bytes();
+ if name_bytes.is_empty() || name_bytes[0] != b'/' {
+ ERRNO.set(EINVAL);
+ return SEM_FAILED;
+ }
+ if name_bytes[1..].iter().any(|&b| b == b'/') {
+ ERRNO.set(EINVAL);
+ return SEM_FAILED;
+ }
+
+ let creat = oflag & O_CREAT == O_CREAT;
+ let excl = oflag & O_EXCL == O_EXCL;
+
+ let (mode, value): (mode_t, c_uint) = if creat {
+ (
+ unsafe { __valist.arg::<mode_t>() },
+ unsafe { __valist.arg::<c_uint>() },
+ )
+ } else {
+ (0, 0)
+ };
+
+ // Open or create the shared memory backing.
+ let (fd, created) = if creat && excl {
+ // O_CREAT | O_EXCL: must create exclusively.
+ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) };
+ if fd < 0 {
+ return SEM_FAILED;
+ }
+ (fd, true)
+ } else if creat {
+ // O_CREAT without O_EXCL: try exclusive first, fall back to open.
+ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) };
+ if fd >= 0 {
+ (fd, true)
+ } else if ERRNO.get() == EEXIST {
+ let fd = unsafe { shm_open(name, O_RDWR, 0) };
+ if fd < 0 {
+ return SEM_FAILED;
+ }
+ (fd, false)
+ } else {
+ return SEM_FAILED;
+ }
+ } else {
+ // No O_CREAT: open existing.
+ let fd = unsafe { shm_open(name, O_RDWR, 0) };
+ if fd < 0 {
+ return SEM_FAILED;
+ }
+ (fd, false)
+ };
+
+ // Set size if we created the backing.
+ if created {
+ if unsafe { ftruncate(fd, size_of::<sem_t>() as off_t) } < 0 {
+ let _ = unsafe { close(fd) };
+ return SEM_FAILED;
+ }
+ }
+
+ // Map the shared memory.
+ let ptr = unsafe {
+ mmap(
+ core::ptr::null_mut(),
+ size_of::<sem_t>(),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ fd,
+ 0,
+ )
+ };
+ let _ = unsafe { close(fd) };
+
+ if ptr == MAP_FAILED {
+ return SEM_FAILED;
+ }
+
+ let sem_ptr = ptr.cast::<sem_t>();
+
+ // Initialize the semaphore value if we created the backing.
+ if created {
+ unsafe { sem_ptr.cast::<RlctSempahore>().write(RlctSempahore::new(value)) };
+ }
+
+ sem_ptr
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_post.html>.
@@ -76,9 +185,9 @@
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_unlink.html>.
-// #[unsafe(no_mangle)]
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int {
- todo!("named semaphores")
+ unsafe { shm_unlink(name) }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_trywait.html>.
--- a/src/header/semaphore/cbindgen.toml 2026-04-25 17:07:53.743979154 +0100
+++ b/src/header/semaphore/cbindgen.toml 2026-04-25 17:09:18.310792692 +0100
@@ -3,6 +3,9 @@
after_includes = """
#include <bits/timespec.h> // for timespec
"""
+trailer = """
+#define SEM_FAILED ((sem_t *) -1)
+"""
language = "C"
style = "Type"
no_includes = true
@@ -1,181 +0,0 @@
diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs
--- a/src/header/stdio/mod.rs
+++ b/src/header/stdio/mod.rs
@@ -46,4 +46,7 @@
pub use self::getdelim::*;
mod getdelim;
+pub use self::open_memstream::*;
+mod open_memstream;
+
mod ext;
diff --git a/src/header/stdio/open_memstream.rs b/src/header/stdio/open_memstream.rs
new file mode 100644
--- /dev/null
+++ b/src/header/stdio/open_memstream.rs
@@ -0,0 +1,124 @@
+use alloc::{boxed::Box, vec, vec::Vec};
+use core::ptr;
+
+use super::{
+ Buffer, FILE,
+ constants::{BUFSIZ, F_NORD},
+};
+use crate::{
+ error::{Errno, ResultExtPtrMut},
+ fs::File,
+ header::{
+ errno::{EFAULT, ENOMEM},
+ fcntl, pthread, stdlib, unistd,
+ },
+ io::{self, BufWriter, Write},
+ platform::{
+ ERRNO,
+ types::{c_char, size_t},
+ },
+};
+
+struct MemstreamWriter {
+ bufp: *mut *mut c_char,
+ sizep: *mut size_t,
+ current: *mut c_char,
+ buffer: Vec<u8>,
+}
+
+unsafe impl Send for MemstreamWriter {}
+
+impl MemstreamWriter {
+ fn new(bufp: *mut *mut c_char, sizep: *mut size_t) -> Self {
+ Self {
+ bufp,
+ sizep,
+ current: ptr::null_mut(),
+ buffer: Vec::new(),
+ }
+ }
+
+ fn sync_output(&mut self) -> io::Result<()> {
+ let size = self.buffer.len();
+ let alloc_size = size
+ .checked_add(1)
+ .ok_or_else(|| io::Error::from_raw_os_error(ENOMEM))?;
+
+ let raw = if self.current.is_null() {
+ unsafe { stdlib::malloc(alloc_size) }
+ } else {
+ unsafe { stdlib::realloc(self.current.cast(), alloc_size) }
+ };
+ if raw.is_null() {
+ return Err(io::Error::from_raw_os_error(ENOMEM));
+ }
+
+ let raw = raw.cast::<c_char>();
+ if size != 0 {
+ unsafe { ptr::copy_nonoverlapping(self.buffer.as_ptr(), raw.cast::<u8>(), size) };
+ }
+ unsafe {
+ *raw.add(size) = 0;
+ *self.bufp = raw;
+ *self.sizep = size;
+ }
+ self.current = raw;
+ Ok(())
+ }
+}
+
+impl Write for MemstreamWriter {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.buffer
+ .try_reserve(buf.len())
+ .map_err(|_| io::Error::from_raw_os_error(ENOMEM))?;
+ self.buffer.extend_from_slice(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.sync_output()
+ }
+}
+
+fn create_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> Result<Box<FILE>, Errno> {
+ if bufp.is_null() || sizep.is_null() {
+ return Err(Errno(EFAULT));
+ }
+
+ unsafe {
+ *bufp = ptr::null_mut();
+ *sizep = 0;
+ }
+
+ let mut fds = [0; 2];
+ if unsafe { unistd::pipe2(fds.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 {
+ return Err(Errno(ERRNO.get()));
+ }
+ let _ = unistd::close(fds[0]);
+
+ let file = File::new(fds[1]);
+ let writer = Box::new(BufWriter::new(MemstreamWriter::new(bufp, sizep)));
+ let mutex_attr = pthread::RlctMutexAttr {
+ ty: pthread::PTHREAD_MUTEX_RECURSIVE,
+ ..Default::default()
+ };
+
+ Ok(Box::new(FILE {
+ lock: pthread::RlctMutex::new(&mutex_attr).unwrap(),
+ file,
+ flags: F_NORD,
+ read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]),
+ read_pos: 0,
+ read_size: 0,
+ unget: Vec::new(),
+ writer,
+ pid: None,
+ orientation: 0,
+ }))
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn open_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> *mut FILE {
+ create_memstream(bufp, sizep).or_errno_null_mut()
+}
diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk
--- a/tests/Makefile.tests.mk
+++ b/tests/Makefile.tests.mk
@@ -85,6 +85,7 @@ VARIED_NAMES=\
stdio/fseek \
stdio/fwrite \
stdio/getc_unget \
- stdio/getline \
+ stdio/getline \
+ stdio/open_memstream \
stdio/mutex \
stdio/popen \
diff --git a/tests/stdio/open_memstream.c b/tests/stdio/open_memstream.c
new file mode 100644
--- /dev/null
+++ b/tests/stdio/open_memstream.c
@@ -0,0 +1,24 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+ char *buf = NULL;
+ size_t size = 0;
+
+ FILE *stream = open_memstream(&buf, &size);
+ assert(stream != NULL);
+ assert(fputs("hello", stream) >= 0);
+ assert(fflush(stream) == 0);
+ assert(size == 5);
+ assert(strcmp(buf, "hello") == 0);
+ assert(fputc('!', stream) != EOF);
+ assert(fclose(stream) == 0);
+ assert(size == 6);
+ assert(strcmp(buf, "hello!") == 0);
+
+ free(buf);
+ puts("open_memstream ok");
+ return 0;
+}
-124
View File
@@ -1,124 +0,0 @@
diff --git a/src/header/sched/mod.rs b/src/header/sched/mod.rs
--- a/src/header/sched/mod.rs
+++ b/src/header/sched/mod.rs
@@ -2,9 +2,11 @@
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sched.h.html>.
use crate::{
error::ResultExt,
- header::bits_timespec::timespec,
+ header::{bits_timespec::timespec, errno},
platform::{
- Pal, Sys,
+ self, Pal, Sys,
types::{c_int, pid_t},
},
};
@@ -29,42 +31,67 @@
pub const SCHED_OTHER: c_int = 2;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_max.html>.
// #[unsafe(no_mangle)]
pub extern "C" fn sched_get_priority_max(policy: c_int) -> c_int {
- todo!()
+ match policy {
+ SCHED_FIFO | SCHED_RR => 99,
+ SCHED_OTHER => 0,
+ _ => {
+ platform::ERRNO.set(errno::EINVAL);
+ -1
+ }
+ }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_max.html>.
// #[unsafe(no_mangle)]
pub extern "C" fn sched_get_priority_min(policy: c_int) -> c_int {
- todo!()
+ match policy {
+ SCHED_FIFO | SCHED_RR => 0,
+ SCHED_OTHER => 0,
+ _ => {
+ platform::ERRNO.set(errno::EINVAL);
+ -1
+ }
+ }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_getparam.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn sched_getparam(pid: pid_t, param: *mut sched_param) -> c_int {
- todo!()
+ if param.is_null() {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ // Redox has no real-time scheduler; return default params
+ (*param).sched_priority = 0;
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_rr_get_interval.html>.
// #[unsafe(no_mangle)]
pub extern "C" fn sched_rr_get_interval(pid: pid_t, time: *const timespec) -> c_int {
- todo!()
+ if time.is_null() {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ // Redox has no real-time scheduler; report a nominal 1-second round-robin interval
+ unsafe {
+ (*(time as *mut timespec)).tv_sec = 1;
+ (*(time as *mut timespec)).tv_nsec = 0;
+ }
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_setparam.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn sched_setparam(pid: pid_t, param: *const sched_param) -> c_int {
- todo!()
+ if param.is_null() {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ let priority = (*param).sched_priority;
+ if priority < 0 || priority > 99 {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ // Redox has no real-time scheduler; validate and succeed as a no-op
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_setscheduler.html>.
// #[unsafe(no_mangle)]
pub extern "C" fn sched_setscheduler(
pid: pid_t,
policy: c_int,
param: *const sched_param,
) -> c_int {
- todo!()
+ if param.is_null() {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ match policy {
+ SCHED_FIFO | SCHED_RR | SCHED_OTHER => {
+ let priority = unsafe { (*param).sched_priority };
+ if priority < 0 || priority > 99 {
+ platform::ERRNO.set(errno::EINVAL);
+ return -1;
+ }
+ // Redox has no real-time scheduler; validate and succeed as a no-op
+ 0
+ }
+ _ => {
+ platform::ERRNO.set(errno::EINVAL);
+ -1
+ }
+ }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_yield.html>.
-120
View File
@@ -1,120 +0,0 @@
diff -ruN a/src/header/signal/mod.rs b/src/header/signal/mod.rs
--- a/src/header/signal/mod.rs 2026-04-15 09:40:30.420306210 +0100
+++ b/src/header/signal/mod.rs 2026-04-15 09:46:42.011891206 +0100
@@ -32,6 +32,9 @@
#[path = "redox.rs"]
pub mod sys;
+mod signalfd;
+pub use self::signalfd::*;
+
type SigSet = BitSet<[u64; 1]>;
pub(crate) const SIG_DFL: usize = 0;
diff -ruN a/src/header/signal/signalfd.rs b/src/header/signal/signalfd.rs
--- a/src/header/signal/signalfd.rs 1970-01-01 00:00:00.000000000 +0000
+++ b/src/header/signal/signalfd.rs 2026-04-15 09:46:42.011930569 +0100
@@ -0,0 +1,103 @@
+use core::{mem, ptr};
+
+use crate::{
+ error::{Errno, ResultExt},
+ header::fcntl::{
+ FD_CLOEXEC, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC, O_NONBLOCK, O_RDWR, fcntl,
+ },
+ platform::{
+ ERRNO, Pal, Sys,
+ types::{c_int, c_ulonglong},
+ },
+};
+
+use super::{SIG_BLOCK, sigprocmask, sigset_t};
+
+pub const SFD_CLOEXEC: c_int = 0x80000;
+pub const SFD_NONBLOCK: c_int = 0x800;
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+pub struct signalfd_siginfo {
+ pub ssi_signo: u32,
+ pub ssi_errno: i32,
+ pub ssi_code: i32,
+ pub ssi_pid: u32,
+ pub ssi_uid: u32,
+ pub ssi_fd: i32,
+ pub ssi_tid: u32,
+ pub ssi_band: u32,
+ pub ssi_overrun: u32,
+ pub ssi_trapno: u32,
+ pub ssi_status: i32,
+ pub ssi_int: i32,
+ pub ssi_ptr: u64,
+ pub ssi_utime: u64,
+ pub ssi_stime: u64,
+ pub ssi_addr: u64,
+ pub ssi_addr_lsb: u16,
+ pub __pad2: u16,
+ pub ssi_syscall: i32,
+ pub ssi_call_addr: u64,
+ pub ssi_arch: u32,
+ pub __pad: [u8; 28],
+}
+
+#[unsafe(no_mangle)]
+pub extern "C" fn _cbindgen_export_signalfd_siginfo(siginfo: signalfd_siginfo) {}
+
+fn signalfd4_inner(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> Result<c_int, Errno> {
+ let supported = SFD_CLOEXEC | SFD_NONBLOCK;
+ if flags & !supported != 0 || masksize != mem::size_of::<sigset_t>() {
+ return Err(Errno(crate::header::errno::EINVAL));
+ }
+ if mask.is_null() {
+ return Err(Errno(crate::header::errno::EFAULT));
+ }
+
+ let new_fd = if fd == -1 {
+ let mut oflag = O_RDWR;
+ if flags & SFD_CLOEXEC == SFD_CLOEXEC {
+ oflag |= O_CLOEXEC;
+ }
+ if flags & SFD_NONBLOCK == SFD_NONBLOCK {
+ oflag |= O_NONBLOCK;
+ }
+ Sys::open(c"/scheme/event".into(), oflag, 0)?
+ } else {
+ if flags & SFD_CLOEXEC == SFD_CLOEXEC
+ && unsafe { fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong) } < 0
+ {
+ return Err(Errno(ERRNO.get()));
+ }
+ if flags & SFD_NONBLOCK == SFD_NONBLOCK {
+ let current = unsafe { fcntl(fd, F_GETFL, 0 as c_ulonglong) };
+ if current < 0 {
+ return Err(Errno(ERRNO.get()));
+ }
+ if unsafe { fcntl(fd, F_SETFL, (current | O_NONBLOCK) as c_ulonglong) } < 0 {
+ return Err(Errno(ERRNO.get()));
+ }
+ }
+ fd
+ };
+
+ if unsafe { sigprocmask(SIG_BLOCK, mask, ptr::null_mut()) } < 0 {
+ if fd == -1 {
+ let _ = Sys::close(new_fd);
+ }
+ return Err(Errno(ERRNO.get()));
+ }
+
+ Ok(new_fd)
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn signalfd4(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> c_int {
+ signalfd4_inner(fd, mask, masksize, flags).or_minus_one_errno()
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn signalfd(fd: c_int, mask: *const sigset_t, masksize: usize) -> c_int {
+ unsafe { signalfd4(fd, mask, masksize, 0) }
+}
@@ -1,98 +0,0 @@
diff -ruN a/src/header/mod.rs b/src/header/mod.rs
--- a/src/header/mod.rs 2026-04-15 09:55:11.441949342 +0100
+++ b/src/header/mod.rs 2026-04-15 09:57:28.904091552 +0100
@@ -92,14 +92,14 @@
pub mod sys_eventfd;
pub mod sys_file;
pub mod sys_ioctl;
-// TODO: sys/ipc.h
+pub mod sys_ipc;
pub mod sys_mman;
// TODO: sys/msg.h
pub mod sys_ptrace;
pub mod sys_resource;
pub mod sys_select;
-// TODO: sys/sem.h
-// TODO: sys/shm.h
+pub mod sys_sem;
+pub mod sys_shm;
pub mod sys_socket;
pub mod sys_stat;
pub mod sys_statvfs;
diff -ruN a/src/header/sys_ipc/cbindgen.toml b/src/header/sys_ipc/cbindgen.toml
--- a/src/header/sys_ipc/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000
+++ b/src/header/sys_ipc/cbindgen.toml 2026-04-15 09:57:28.904120977 +0100
@@ -0,0 +1,12 @@
+sys_includes = ["sys/types.h"]
+include_guard = "_SYS_IPC_H"
+trailer = """
+typedef struct ipc_perm ipc_perm;
+"""
+language = "C"
+style = "Tag"
+no_includes = true
+cpp_compat = true
+
+[enum]
+prefix_with_name = true
diff -ruN a/src/header/sys_ipc/mod.rs b/src/header/sys_ipc/mod.rs
--- a/src/header/sys_ipc/mod.rs 1970-01-01 00:00:00.000000000 +0000
+++ b/src/header/sys_ipc/mod.rs 2026-04-15 09:57:28.904159138 +0100
@@ -0,0 +1,31 @@
+//! `sys/ipc.h` implementation.
+
+use crate::platform::types::{c_int, c_ushort};
+
+pub type key_t = c_int;
+
+pub const IPC_PRIVATE: key_t = 0;
+pub const IPC_CREAT: c_int = 0o1000;
+pub const IPC_EXCL: c_int = 0o2000;
+pub const IPC_NOWAIT: c_int = 0o4000;
+
+pub const IPC_RMID: c_int = 0;
+pub const IPC_SET: c_int = 1;
+pub const IPC_STAT: c_int = 2;
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+pub struct ipc_perm {
+ pub __key: key_t,
+ pub uid: c_ushort,
+ pub gid: c_ushort,
+ pub cuid: c_ushort,
+ pub cgid: c_ushort,
+ pub mode: c_ushort,
+ pub __seq: c_ushort,
+}
+
+#[unsafe(no_mangle)]
+pub extern "C" fn _cbindgen_export_ipc_perm(value: ipc_perm) {
+ let _ = value;
+}
diff -ruN a/src/header/sys_sem/cbindgen.toml b/src/header/sys_sem/cbindgen.toml
--- a/src/header/sys_sem/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000
+++ b/src/header/sys_sem/cbindgen.toml 2026-04-15 09:57:28.904183804 +0100
@@ -0,0 +1,9 @@
+sys_includes = ["sys/types.h", "sys/ipc.h", "stdint.h"]
+include_guard = "_SYS_SEM_H"
+language = "C"
+style = "Tag"
+no_includes = true
+cpp_compat = true
+
+[enum]
+prefix_with_name = true
diff -ruN a/src/header/sys_shm/cbindgen.toml b/src/header/sys_shm/cbindgen.toml
--- a/src/header/sys_shm/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000
+++ b/src/header/sys_shm/cbindgen.toml 2026-04-15 09:57:28.904207067 +0100
@@ -0,0 +1,9 @@
+sys_includes = ["sys/types.h", "sys/ipc.h", "sys/mman.h", "stdint.h"]
+include_guard = "_SYS_SHM_H"
+language = "C"
+style = "Tag"
+no_includes = true
+cpp_compat = true
+
+[enum]
+prefix_with_name = true
@@ -1,59 +0,0 @@
diff --git a/src/platform/redox/socket.rs b/src/platform/redox/socket.rs
index d223c36f..f8a1c2e0 100644
--- a/src/platform/redox/socket.rs
+++ b/src/platform/redox/socket.rs
@@ -774,6 +774,21 @@ impl PalSocket for Sys {
return Ok(());
}
},
+ crate::header::sys_socket::constants::IPPROTO_TCP => {
+ let metadata = [SocketCall::GetSockOpt as u64, option_name as u64];
+ let payload =
+ unsafe { slice::from_raw_parts_mut(option_value as *mut u8, option_len) };
+ let call_flags = CallFlags::empty();
+ unsafe {
+ *option_len_ptr = redox_rt::sys::sys_call_ro(
+ socket as usize,
+ payload,
+ CallFlags::empty(),
+ &metadata,
+ )? as socklen_t;
+ }
+ return Ok(());
+ }
_ => (),
}
@@ -1069,18 +1069,13 @@ impl PalSocket for Sys {
crate::header::sys_socket::constants::IPPROTO_TCP => {
- match option_name {
- crate::header::sys_socket::constants::TCP_NODELAY => {
- let metadata = [SocketCall::SetSockOpt as u64, option_name as u64];
- let payload = unsafe {
- slice::from_raw_parts(option_value as *const u8, option_len as usize)
- };
- redox_rt::sys::sys_call_wo(
- socket as usize,
- payload,
- CallFlags::empty(),
- &metadata,
- )?;
- return Ok(());
- }
- _ => (),
- }
+ let metadata = [SocketCall::SetSockOpt as u64, option_name as u64];
+ let payload = unsafe {
+ slice::from_raw_parts(option_value as *const u8, option_len as usize)
+ };
+ redox_rt::sys::sys_call_wo(
+ socket as usize,
+ payload,
+ CallFlags::empty(),
+ &metadata,
+ )?;
+ return Ok(());
}
_ => (),
}
-13
View File
@@ -1,13 +0,0 @@
diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs
--- a/src/header/unistd/mod.rs
+++ b/src/header/unistd/mod.rs
@@ -1262,9 +1262,9 @@
/// Specifications Issue 6, and removed in Issue 7.
#[deprecated]
// #[unsafe(no_mangle)]
pub extern "C" fn vfork() -> pid_t {
- unimplemented!();
+ unsafe { fork() }
}
unsafe fn with_argv(
@@ -1,319 +0,0 @@
diff --git a/redox-rt/src/lib.rs b/redox-rt/src/lib.rs
index 12835a6..3e99860 100644
--- a/redox-rt/src/lib.rs
+++ b/redox-rt/src/lib.rs
@@ -18,6 +18,8 @@ use self::{
extern crate alloc;
+
+use alloc::vec::Vec;
#[macro_export]
macro_rules! asmfunction(
@@ -224,6 +226,7 @@ pub unsafe fn initialize(
rgid: metadata.rgid,
sgid: metadata.sgid,
ns_fd,
+ groups: Vec::new(),
};
}
}
@@ -241,6 +244,7 @@ pub struct DynamicProcInfo {
pub rgid: u32,
pub sgid: u32,
pub ns_fd: Option<FdGuardUpper>,
+ pub groups: Vec<u32>,
}
static DYNAMIC_PROC_INFO: Mutex<DynamicProcInfo> = Mutex::new(DynamicProcInfo {
@@ -252,6 +256,7 @@ static DYNAMIC_PROC_INFO: Mutex<DynamicProcInfo> = Mutex::new(DynamicProcInfo {
egid: u32::MAX,
sgid: u32::MAX,
ns_fd: None,
+ groups: Vec::new(),
});
#[inline]
diff --git a/redox-rt/src/proc.rs b/redox-rt/src/proc.rs
index 48cce34..7c0cdb7 100644
--- a/redox-rt/src/proc.rs
+++ b/redox-rt/src/proc.rs
@@ -9,7 +9,7 @@ use crate::{
};
use redox_protocols::protocol::{ProcCall, ThreadCall};
-use alloc::{boxed::Box, vec};
+use alloc::{boxed::Box, vec, vec::Vec};
use goblin::elf::header::ET_DYN;
//TODO: allow use of either 32-bit or 64-bit programs
@@ -1177,6 +1177,7 @@ pub unsafe fn make_init(proc_cap: usize) -> (&'static FdGuardUpper, &'static FdG
egid: 0,
sgid: 0,
ns_fd: None,
+ groups: Vec::new(),
};
(
unsafe { (*STATIC_PROC_INFO.get()).proc_fd.as_ref().unwrap() },
diff --git a/redox-rt/src/sys.rs b/redox-rt/src/sys.rs
index f0363a3..fb9fc52 100644
--- a/redox-rt/src/sys.rs
+++ b/redox-rt/src/sys.rs
@@ -18,6 +18,7 @@ use crate::{
signal::tmp_disable_signals,
};
+use alloc::vec;
use alloc::vec::Vec;
use redox_protocols::protocol::{
NsDup, ProcCall, ProcKillTarget, RtSigInfo, ThreadCall, WaitFlags,
@@ -415,6 +416,54 @@ pub fn posix_getresugid() -> Resugid<u32> {
sgid,
}
}
+pub fn posix_setgroups(groups: &[u32]) -> Result<()> {
+ let _sig_guard = tmp_disable_signals();
+
+ let mut buf = Vec::with_capacity(groups.len() * size_of::<u32>());
+ for gid in groups {
+ buf.extend_from_slice(&gid.to_ne_bytes());
+ }
+
+ let auth_fd = crate::current_proc_fd().as_raw_fd();
+ let groups_path = alloc::format!("auth-{}-groups", auth_fd);
+
+ let thr_fd = crate::RtTcb::current().thread_fd();
+ let groups_fd = thr_fd.dup(groups_path.as_bytes())?;
+
+ syscall::write(groups_fd.as_raw_fd(), &buf)?;
+
+ let mut guard = DYNAMIC_PROC_INFO.lock();
+ guard.groups = groups.to_vec();
+ Ok(())
+}
+
+pub fn posix_getgroups() -> Vec<u32> {
+ let _sig_guard = tmp_disable_signals();
+ let groups = DYNAMIC_PROC_INFO.lock().groups.clone();
+ if !groups.is_empty() {
+ return groups;
+ }
+ drop(_sig_guard);
+ posix_readback_groups().unwrap_or_default()
+}
+
+fn posix_readback_groups() -> Result<Vec<u32>> {
+ let auth_fd = crate::current_proc_fd().as_raw_fd();
+ let groups_path = alloc::format!("auth-{}-groups", auth_fd);
+ let thr_fd = crate::RtTcb::current().thread_fd();
+ let groups_fd = thr_fd.dup(groups_path.as_bytes())?;
+
+ let mut buf = vec![0u8; 65536 * size_of::<u32>()];
+ let n = syscall::read(groups_fd.as_raw_fd(), &mut buf)?;
+ let count = n / size_of::<u32>();
+ let mut groups = Vec::with_capacity(count);
+ for chunk in buf[..n].chunks_exact(size_of::<u32>()) {
+ groups.push(u32::from_ne_bytes(<[u8; size_of::<u32>()]>::try_from(chunk).unwrap()));
+ }
+ let mut guard = DYNAMIC_PROC_INFO.lock();
+ guard.groups = groups.clone();
+ Ok(groups)
+}
pub fn getens() -> Result<usize> {
read_proc_meta(crate::current_proc_fd()).map(|meta| meta.ens as usize)
}
diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs
index 752339a..a0b4304 100644
--- a/src/platform/redox/mod.rs
+++ b/src/platform/redox/mod.rs
@@ -43,7 +43,7 @@ use crate::{
sys_file,
sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE},
sys_random,
- sys_resource::{RLIM_INFINITY, rlimit, rusage},
+ sys_resource::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, RLIM_INFINITY, rlimit, rusage},
sys_select::timeval,
sys_stat::{S_ISVTX, stat},
sys_statvfs::statvfs,
@@ -605,51 +605,17 @@ impl Pal for Sys {
}
fn getgroups(mut list: Out<[gid_t]>) -> Result<c_int> {
- // FIXME: this operation doesn't scale when group/passwd file grows
-
- let uid = Self::geteuid();
- let pwd = crate::header::pwd::getpwuid(uid);
-
- if pwd.is_null() {
- return Err(Errno(ENOENT));
- }
-
- let username = unsafe { CStr::from_ptr((*pwd).pw_name) };
- let username = username.to_bytes_with_nul();
- let mut count = 0;
-
- unsafe {
- use crate::header::grp;
- grp::setgrent();
-
- while let Some(grp) = grp::getgrent().as_ref() {
- let mut i = 0;
- let mut found = false;
-
- while !(*grp.gr_mem.offset(i)).is_null() {
- let member = CStr::from_ptr(*grp.gr_mem.offset(i));
- if member.to_bytes_with_nul() == username {
- found = true;
- break;
- }
- i += 1;
- }
-
- if found {
- if !list.is_empty() && (count as usize) < list.len() {
- list.index(count).write(grp.gr_gid);
- }
- count += 1;
- }
+ let groups = redox_rt::sys::posix_getgroups();
+ let count = groups.len();
+ if !list.is_empty() {
+ if count > list.len() {
+ return Err(Errno(EINVAL));
+ }
+ for (i, gid) in groups.iter().enumerate() {
+ list.index(i as _).write(*gid as gid_t);
}
- grp::endgrent();
- }
-
- if !list.is_empty() && (count as usize) > list.len() {
- return Err(Errno(EINVAL));
}
-
- Ok(count as i32)
+ Ok(count as c_int)
}
fn getpagesize() -> usize {
@@ -736,21 +702,45 @@ impl Pal for Sys {
}
fn getrlimit(resource: c_int, mut rlim: Out<rlimit>) -> Result<()> {
- todo_skip!(0, "getrlimit({}, {:p}): not implemented", resource, rlim);
- rlim.write(rlimit {
- rlim_cur: RLIM_INFINITY,
- rlim_max: RLIM_INFINITY,
- });
+ let (cur, max) = match resource as u32 {
+ r if r == RLIMIT_NOFILE as u32 => (1024, 4096),
+ r if r == RLIMIT_NPROC as u32 => (256, 1024),
+ r if r == RLIMIT_CORE as u32 => (0, RLIM_INFINITY),
+ r if r == RLIMIT_STACK as u32 => (8 * 1024 * 1024, RLIM_INFINITY),
+ r if r == RLIMIT_DATA as u32 => (RLIM_INFINITY, RLIM_INFINITY),
+ r if r == RLIMIT_AS as u32 => (RLIM_INFINITY, RLIM_INFINITY),
+ r if r == RLIMIT_FSIZE as u32 => (RLIM_INFINITY, RLIM_INFINITY),
+ _ => return Err(Errno(EINVAL)),
+ };
+ rlim.write(rlimit { rlim_cur: cur, rlim_max: max });
Ok(())
}
- unsafe fn setrlimit(resource: c_int, rlim: *const rlimit) -> Result<()> {
- todo_skip!(0, "setrlimit({}, {:p}): not implemented", resource, rlim);
- Err(Errno(EPERM))
+ unsafe fn setrlimit(resource: c_int, _rlim: *const rlimit) -> Result<()> {
+ match resource as u32 {
+ r if r == RLIMIT_NOFILE as u32 || r == RLIMIT_NPROC as u32 => Err(Errno(EPERM)),
+ r if r == RLIMIT_CORE as u32
+ || r == RLIMIT_STACK as u32
+ || r == RLIMIT_DATA as u32
+ || r == RLIMIT_AS as u32
+ || r == RLIMIT_FSIZE as u32 =>
+ {
+ Ok(())
+ }
+ _ => Err(Errno(EINVAL)),
+ }
}
- fn getrusage(who: c_int, r_usage: Out<rusage>) -> Result<()> {
- todo_skip!(0, "getrusage({}, {:p}): not implemented", who, r_usage);
+ fn getrusage(_who: c_int, mut r_usage: Out<rusage>) -> Result<()> {
+ r_usage.write(rusage {
+ ru_utime: timeval { tv_sec: 0, tv_usec: 0 },
+ ru_stime: timeval { tv_sec: 0, tv_usec: 0 },
+ ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0,
+ ru_minflt: 0, ru_majflt: 0, ru_nswap: 0,
+ ru_inblock: 0, ru_oublock: 0,
+ ru_msgsnd: 0, ru_msgrcv: 0, ru_nsignals: 0,
+ ru_nvcsw: 0, ru_nivcsw: 0,
+ });
Ok(())
}
@@ -913,23 +903,7 @@ impl Pal for Sys {
Ok(())
}
- unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> {
- todo_skip!(
- 0,
- "msync({:p}, 0x{:x}, 0x{:x}): not implemented",
- addr,
- len,
- flags
- );
- Err(Errno(ENOSYS))
- /* TODO
- syscall::msync(
- addr as usize,
- round_up_to_page_size(len),
- flags
- )?;
- */
- }
+ unsafe fn msync(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) }
unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> {
// Redox never swaps
@@ -953,16 +927,7 @@ impl Pal for Sys {
Ok(())
}
- unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> {
- todo_skip!(
- 0,
- "madvise({:p}, 0x{:x}, 0x{:x}): not implemented",
- addr,
- len,
- flags
- );
- Err(Errno(ENOSYS))
- }
+ unsafe fn madvise(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) }
unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> {
let redox_rqtp = unsafe { redox_timespec::from(&*rqtp) };
@@ -1220,9 +1185,19 @@ impl Pal for Sys {
}
unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()> {
- // TODO
- todo_skip!(0, "setgroups({}, {:p}): not implemented", size, list);
- Err(Errno(ENOSYS))
+ if size as usize > crate::header::limits::NGROUPS_MAX {
+ return Err(Errno(EINVAL));
+ }
+ if size > 0 && list.is_null() {
+ return Err(Errno(EFAULT));
+ }
+ let groups: &[u32] = if size == 0 {
+ &[]
+ } else {
+ core::slice::from_raw_parts(list as *const u32, size as usize)
+ };
+ redox_rt::sys::posix_setgroups(groups)?;
+ Ok(())
}
fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> {
@@ -1,188 +0,0 @@
diff --git a/src/lib.rs b/src/lib.rs
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,61 +57,201 @@ pub mod start;
pub mod sync;
-use crate::platform::{Allocator, NEWALLOCATOR};
+use crate::platform::{Allocator, NEWALLOCATOR, Pal, Sys};
#[global_allocator]
static ALLOCATOR: Allocator = NEWALLOCATOR;
+
+const MAX_FATAL_BACKTRACE_FRAMES: usize = 16;
+const MAX_FATAL_FRAME_STRIDE: usize = 1024 * 1024;
+
+#[inline(never)]
+fn write_process_thread_identity(w: &mut platform::FileWriter) {
+ use core::fmt::Write;
+
+ let pid = Sys::getpid();
+ let tid = Sys::gettid();
+
+ match crate::pthread::current_thread() {
+ Some(thread) => {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC CONTEXT: pid={} tid={} pthread={:#x}\n",
+ pid,
+ tid,
+ thread as *const _ as usize,
+ ));
+ }
+ None => {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC CONTEXT: pid={} tid={} pthread=<unavailable>\n",
+ pid, tid,
+ ));
+ }
+ }
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+#[inline(never)]
+fn current_frame_pointer() -> *const usize {
+ let frame: *const usize;
+
+ #[cfg(target_arch = "x86_64")]
+ unsafe {
+ core::arch::asm!("mov {}, rbp", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ core::arch::asm!("mov {}, ebp", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ unsafe {
+ core::arch::asm!("mov {}, x29", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ frame
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+fn read_backtrace_frame(frame: *const usize) -> Option<(*const usize, usize)> {
+ let align = core::mem::align_of::<usize>();
+ let frame_addr = frame as usize;
+
+ if frame.is_null() || frame_addr % align != 0 {
+ return None;
+ }
+
+ let next_frame = unsafe { frame.read() } as *const usize;
+ let return_address = unsafe { frame.add(1).read() };
+
+ if return_address == 0 {
+ return None;
+ }
+
+ Some((next_frame, return_address))
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+fn is_sane_next_backtrace_frame(current: *const usize, next: *const usize) -> bool {
+ let align = core::mem::align_of::<usize>();
+ let current_addr = current as usize;
+ let next_addr = next as usize;
+
+ !next.is_null()
+ && next_addr % align == 0
+ && next_addr > current_addr
+ && next_addr - current_addr <= MAX_FATAL_FRAME_STRIDE
+}
+
+#[inline(never)]
+fn write_best_effort_backtrace(w: &mut platform::FileWriter) {
+ use core::fmt::Write;
+
+ let _ = w.write_str("RELIBC: attempting best-effort backtrace\n");
+
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+ {
+ let mut frame = current_frame_pointer();
+ let mut wrote_frame = false;
+
+ for frame_index in 0..MAX_FATAL_BACKTRACE_FRAMES {
+ let Some((next_frame, return_address)) = read_backtrace_frame(frame) else {
+ break;
+ };
+
+ wrote_frame = true;
+ let _ = w.write_fmt(format_args!(
+ "RELIBC BACKTRACE[{frame_index:02}]: {:#x}\n",
+ return_address,
+ ));
+
+ if !is_sane_next_backtrace_frame(frame, next_frame) {
+ break;
+ }
+
+ frame = next_frame;
+ }
+
+ if !wrote_frame {
+ let _ = w.write_str("RELIBC: backtrace attempt produced no frames\n");
+ }
+ }
+
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
+ {
+ let _ = w.write_str("RELIBC: backtrace unavailable on this architecture\n");
+ }
+}
#[unsafe(no_mangle)]
pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter::new(2);
- let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
+
+ if let Some(location) = pi.location() {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC PANIC LOCATION: {}:{}:{}\n",
+ location.file(),
+ location.line(),
+ location.column(),
+ ));
+ } else {
+ let _ = w.write_str("RELIBC PANIC LOCATION: <unavailable>\n");
+ }
+
+ write_process_thread_identity(&mut w);
+ let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
core::intrinsics::abort();
}
@@ -95,23 +235,27 @@ pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
let mut w = platform::FileWriter::new(2);
let _ = w.write_fmt(format_args!(
- "RELIBC OOM: {} bytes aligned to {} bytes\n",
+ "RELIBC OOM: {} bytes aligned to {} bytes - process will abort\n",
layout.size(),
layout.align()
));
+ write_process_thread_identity(&mut w);
+ write_best_effort_backtrace(&mut w);
core::intrinsics::abort();
}
#[cfg(not(test))]
#[allow(non_snake_case)]
#[linkage = "weak"]
#[unsafe(no_mangle)]
pub extern "C" fn _Unwind_Resume() -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter::new(2);
- let _ = w.write_str("_Unwind_Resume\n");
+ let _ = w.write_str(
+ "RELIBC: _Unwind_Resume called - exception propagation failed, aborting\n",
+ );
+ write_process_thread_identity(&mut w);
core::intrinsics::abort();
}
@@ -1,87 +0,0 @@
Fix ENOTRECOVERABLE returned for non-robust mutexes and register main
thread in OS_TID_TO_PTHREAD.
The robust mutex liveness check (mutex_owner_id_is_live) was returning
ENOTRECOVERABLE for non-robust mutexes when the owner appeared dead.
Per POSIX, the behaviour of a non-robust mutex whose owner has died is
undefined; returning an error crashes every Rust std::sync::Mutex user.
For lock_inner, fall through to spin/futex-wait instead. For try_lock,
return EBUSY instead.
Additionally, pthread::init() never registered the main thread in
OS_TID_TO_PTHREAD, so any mutex owned by the main thread would always
appear to have a dead owner, making the liveness check unreliable.
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
index 8243a48..c455a67 100644
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -43,9 +43,13 @@ pub unsafe fn init() {
thread.stack_size = STACK_SIZE;
}
- unsafe { Tcb::current() }
- .expect_notls("no TCB present for main thread")
- .pthread = thread;
+ let tcb = unsafe { Tcb::current() }
+ .expect_notls("no TCB present for main thread");
+ tcb.pthread = thread;
+
+ OS_TID_TO_PTHREAD
+ .lock()
+ .insert(Sys::current_os_tid(), ForceSendSync(tcb as *const Tcb as *mut Tcb));
}
//static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1);
diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs
index af0c429..1b2b3ca 100644
--- a/src/sync/pthread_mutex.rs
+++ b/src/sync/pthread_mutex.rs
@@ -136,14 +136,17 @@ impl RlctMutex {
Err(thread) => {
let owner = thread & INDEX_MASK;
- if !crate::pthread::mutex_owner_id_is_live(owner) {
- if !self.robust {
- return Err(Errno(ENOTRECOVERABLE));
- }
-
+ if !crate::pthread::mutex_owner_id_is_live(owner) && self.robust {
let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
match self.inner.compare_exchange(
thread,
@@ -152,6 +155,12 @@ impl RlctMutex {
Ok(_) => return self.finish_lock_acquire(true),
Err(_) => continue,
}
+ } else if !crate::pthread::mutex_owner_id_is_live(owner) {
+ // Non-robust mutex with apparently-dead owner: per POSIX the
+ // behaviour is undefined. We conservatively keep spinning /
+ // futex-waiting rather than returning ENOTRECOVERABLE, which
+ // would crash any Rust std::sync::Mutex user.
}
if spins_left > 0 {
@@ -241,14 +250,17 @@ impl RlctMutex {
if current & FUTEX_OWNER_DIED != 0 || (owner != 0 && !crate::pthread::mutex_owner_id_is_live(owner)) {
- if !self.robust {
- return Err(Errno(ENOTRECOVERABLE));
- }
-
+ if self.robust {
let new_value = (current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
match self.inner.compare_exchange(
current,
@@ -257,6 +269,11 @@ impl RlctMutex {
Ok(_) => return self.finish_lock_acquire(true),
Err(_) => continue,
}
+ } else {
+ // Non-robust mutex: owner appears dead but POSIX behaviour is
+ // undefined; report busy rather than ENOTRECOVERABLE.
+ return Err(Errno(EBUSY));
+ }
}
return Err(Errno(EBUSY));
@@ -1,101 +0,0 @@
diff --git a/src/start.rs b/src/start.rs
--- a/src/start.rs
+++ b/src/start.rs
@@ -1,8 +1,6 @@
//! Startup code.
use alloc::{boxed::Box, vec::Vec};
-use core::{intrinsics, ptr};
-
-#[cfg(target_os = "redox")]
-use generic_rt::ExpectTlsFree;
+use core::{fmt::Write, intrinsics, panic::AssertUnwindSafe, ptr};
use crate::{
ALLOCATOR,
@@ -143,6 +141,28 @@ fn io_init() {
stdio::stderr = stdio::default_stderr().get();
}
}
+
+fn catch_unwind<F: FnOnce()>(f: AssertUnwindSafe<F>) -> Result<(), ()> {
+ fn do_call<F: FnOnce()>(data: *mut u8) {
+ let callback = unsafe { &mut *data.cast::<Option<AssertUnwindSafe<F>>>() };
+ if let Some(callback) = callback.take() {
+ callback.0();
+ }
+ }
+
+ fn do_catch<F: FnOnce()>(_data: *mut u8, _payload: *mut u8) {}
+
+ let mut callback = Some(f);
+ let panicked = unsafe {
+ intrinsics::catch_unwind(
+ do_call::<F>,
+ (&mut callback as *mut Option<AssertUnwindSafe<F>>).cast(),
+ do_catch::<F>,
+ ) != 0
+ };
+
+ if panicked { Err(()) } else { Ok(()) }
+}
+
#[cold]
fn abort_startup(args: core::fmt::Arguments<'_>) -> ! {
let mut w = platform::FileWriter::new(2);
@@ -164,15 +184,24 @@ pub unsafe extern "C" fn relibc_start_v1(
unsafe { relibc_verify_host() };
#[cfg(target_os = "redox")]
- let thr_fd = redox_rt::proc::FdGuard::new(
- unsafe {
- crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD)
- }
- .expect_notls("no thread fd present"),
- )
- .to_upper()
- .expect_notls("failed to move thread fd to upper table");
+ let thr_fd = {
+ let thr_fd = match unsafe {
+ crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD)
+ } {
+ Some(thr_fd) => thr_fd,
+ None => abort_startup(format_args!(
+ "relibc_start_v1: missing AT_REDOX_THR_FD auxv entry; no thread fd present\n"
+ )),
+ };
+
+ match redox_rt::proc::FdGuard::new(thr_fd).to_upper() {
+ Ok(thr_fd) => thr_fd,
+ Err(err) => abort_startup(format_args!(
+ "relibc_start_v1: failed to move thread fd to upper table: {err:?}\n"
+ )),
+ }
+ };
// Initialize TLS, if necessary
unsafe {
@@ -237,7 +266,10 @@ pub unsafe extern "C" fn relibc_start_v1(
let mut f = unsafe { &__preinit_array_start } as *const _;
#[allow(clippy::op_ref)]
while f < &raw const __preinit_array_end {
- (unsafe { *f })();
+ let func = unsafe { *f };
+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() {
+ log_initializer_panic(".preinit_array", func);
+ }
f = unsafe { f.offset(1) };
}
}
@@ -247,7 +279,10 @@ pub unsafe extern "C" fn relibc_start_v1(
let mut f = unsafe { &__init_array_start } as *const _;
#[allow(clippy::op_ref)]
while f < &raw const __init_array_end {
- (unsafe { *f })();
+ let func = unsafe { *f };
+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() {
+ log_initializer_panic(".init_array", func);
+ }
f = unsafe { f.offset(1) };
}
}
@@ -1,104 +0,0 @@
diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs
--- a/src/platform/redox/mod.rs
+++ b/src/platform/redox/mod.rs
@@ -77,11 +77,74 @@ static mut BRK_CUR: *mut c_void = ptr::null_mut();
static mut BRK_END: *mut c_void = ptr::null_mut();
const PAGE_SIZE: usize = 4096;
+const NICE_MIN: c_int = -20;
+const NICE_MAX: c_int = 19;
fn round_up_to_page_size(val: usize) -> Option<usize> {
val.checked_add(PAGE_SIZE)
.map(|val| (val - 1) / PAGE_SIZE * PAGE_SIZE)
}
+
+fn is_current_process_priority_target(which: c_int, who: id_t) -> bool {
+ which == crate::header::sys_resource::PRIO_PROCESS
+ && (who == 0 || who == redox_rt::sys::posix_getpid() as id_t)
+}
+
+fn current_process_thread_handle(index: usize) -> Result<Option<FdGuard>> {
+ let thread_name = format!("thread-{index}");
+ match redox_rt::current_proc_fd().dup(thread_name.as_bytes()) {
+ Ok(thread_fd) => Ok(Some(thread_fd)),
+ Err(error) if error.errno == ENOENT => Ok(None),
+ Err(error) => Err(Errno(error.errno)),
+ }
+}
+
+fn current_process_priority_handle(index: usize) -> Result<Option<FdGuard>> {
+ let Some(thread_fd) = current_process_thread_handle(index)? else {
+ return Ok(None);
+ };
+
+ thread_fd
+ .dup(b"priority")
+ .map(Some)
+ .map_err(|error| Errno(error.errno))
+}
+
+fn read_current_process_nice() -> Result<c_int> {
+ let Some(priority_fd) = current_process_priority_handle(0)? else {
+ return Err(Errno(ESRCH));
+ };
+
+ let mut nice_bytes = [0_u8; size_of::<c_int>()];
+ if priority_fd.read(&mut nice_bytes)? != size_of::<c_int>() {
+ return Err(Errno(EIO));
+ }
+
+ Ok(c_int::from_ne_bytes(nice_bytes))
+}
+
+fn write_current_process_nice(nice: c_int) -> Result<()> {
+ let mut updated_threads = 0;
+ let nice_bytes = nice.to_ne_bytes();
+
+ for index in 0.. {
+ let Some(priority_fd) = current_process_priority_handle(index)? else {
+ break;
+ };
+
+ if priority_fd.write(&nice_bytes)? != nice_bytes.len() {
+ return Err(Errno(EIO));
+ }
+ updated_threads += 1;
+ }
+
+ if updated_threads == 0 {
+ return Err(Errno(ESRCH));
+ }
+
+ Ok(())
+}
fn cvt_uid(id: c_int) -> Result<Option<u32>> {
if id == -1 {
return Ok(None);
@@ -698,6 +761,11 @@ impl Pal for Sys {
}
fn getpriority(which: c_int, who: id_t) -> Result<c_int> {
+ if is_current_process_priority_target(which, who) {
+ let nice = read_current_process_nice()?;
+ return Ok(20 - nice);
+ }
+
match redox_rt::sys::posix_getpriority(which, who as u32) {
Ok(kernel_prio) => {
let posix_prio = (kernel_prio as i32 * -1) + 40 as i32;
@@ -1274,7 +1342,12 @@ impl Pal for Sys {
}
fn setpriority(which: c_int, who: id_t, prio: c_int) -> Result<()> {
- let clamped_prio = prio.clamp(-20, 19);
+ let clamped_prio = prio.clamp(NICE_MIN, NICE_MAX);
+
+ if is_current_process_priority_target(which, who) {
+ return write_current_process_nice(clamped_prio);
+ }
+
let kernel_prio = (20 + clamped_prio) as u32;
match redox_rt::sys::posix_setpriority(which, who as u32, kernel_prio) {
@@ -1,43 +0,0 @@
diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs
index 2871a6149..3c8e73f15 100644
--- a/src/sync/pthread_mutex.rs
+++ b/src/sync/pthread_mutex.rs
@@ -35,7 +35,7 @@ const FUTEX_OWNER_DIED: u32 = 1 << 30;
const INDEX_MASK: u32 = !(WAITING_BIT | FUTEX_OWNER_DIED);
// TODO: Lower limit is probably better.
const RECURSIVE_COUNT_MAX_INCLUSIVE: u32 = u32::MAX;
-const SPIN_COUNT: usize = 0;
+const SPIN_COUNT: usize = 100;
impl RlctMutex {
pub(crate) fn new(attr: &RlctMutexAttr) -> Result<Self, Errno> {
diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs
index b5847b5..a8e3c2f0 100644
--- a/src/sync/barrier.rs
+++ b/src/sync/barrier.rs
@@ -47,6 +47,9 @@ impl Barrier {
cvar: FutexState::new(count.get()),
}
}
+ pub fn destroy(&self) {}
+
pub fn wait(&self) -> WaitResult {
let _ = &self.lock;
let sense = self.cvar.sense.load(Ordering::Acquire);
diff --git a/src/header/pthread/barrier.rs b/src/header/pthread/barrier.rs
index 1a5df3a..e69e2b9 100644
--- a/src/header/pthread/barrier.rs
+++ b/src/header/pthread/barrier.rs
@@ -24,10 +24,10 @@ pub(crate) struct RlctBarrierAttr {
// Not async-signal-safe.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_barrier_destroy(barrier: *mut pthread_barrier_t) -> c_int {
- // Behavior is undefined if any thread is currently waiting when this is called.
-
- // No-op, currently.
- unsafe { core::ptr::drop_in_place(barrier.cast::<RlctBarrier>()) };
+ let barrier = unsafe { &*barrier.cast::<RlctBarrier>() };
+ barrier.destroy();
0
}
@@ -1,35 +0,0 @@
Fix ENOTRECOVERABLE returned for non-robust mutexes and register main
thread in OS_TID_TO_PTHREAD.
The robust mutex liveness check (mutex_owner_id_is_live) was returning
ENOTRECOVERABLE for non-robust mutexes when the owner appeared dead.
Per POSIX, the behaviour of a non-robust mutex whose owner has died is
undefined; returning an error crashes every Rust std::sync::Mutex user.
For lock_inner, fall through to spin/futex-wait instead. For try_lock,
return EBUSY instead.
Additionally, pthread::init() never registered the main thread in
OS_TID_TO_PTHREAD, so any mutex owned by the main thread would always
appear to have a dead owner, making the liveness check unreliable.
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
index 8243a48..c455a67 100644
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -43,9 +43,13 @@ pub unsafe fn init() {
thread.stack_size = STACK_SIZE;
}
- unsafe { Tcb::current() }
- .expect_notls("no TCB present for main thread")
- .pthread = thread;
+ let tcb = unsafe { Tcb::current() }
+ .expect_notls("no TCB present for main thread");
+ tcb.pthread = thread;
+
+ OS_TID_TO_PTHREAD
+ .lock()
+ .insert(Sys::current_os_tid(), ForceSendSync(tcb as *const Tcb as *mut Tcb));
}
//static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1);
-127
View File
@@ -1,127 +0,0 @@
diff --git a/src/header/fcntl/mod.rs b/src/header/fcntl/mod.rs
--- a/src/header/fcntl/mod.rs
+++ b/src/header/fcntl/mod.rs
@@ -9,0 +10 @@
+ header::unistd::close,
@@ -75,0 +77,17 @@
+
+ if cmd == F_DUPFD_CLOEXEC {
+ let new_fd = Sys::fcntl(fildes, F_DUPFD_CLOEXEC, arg).or_minus_one_errno();
+ if new_fd >= 0 {
+ return new_fd;
+ }
+
+ let new_fd = Sys::fcntl(fildes, F_DUPFD, arg).or_minus_one_errno();
+ if new_fd < 0 {
+ return -1;
+ }
+ if Sys::fcntl(new_fd, F_SETFD, FD_CLOEXEC as c_ulonglong).or_minus_one_errno() < 0 {
+ let _ = close(new_fd);
+ return -1;
+ }
+ return new_fd;
+ }
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -2,6 +2,7 @@
use core::{
cell::UnsafeCell,
+ panic::AssertUnwindSafe,
ptr,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
@@ -208,11 +209,39 @@ pub(crate) unsafe fn create(
}
/// A shim to wrap thread entry points in logic to set up TLS, for example
+fn catch_unwind<F: FnOnce()>(f: AssertUnwindSafe<F>) -> Result<(), ()> {
+ fn do_call<F: FnOnce()>(data: *mut u8) {
+ let callback = unsafe { &mut *data.cast::<Option<AssertUnwindSafe<F>>>() };
+ if let Some(callback) = callback.take() {
+ callback.0();
+ }
+ }
+
+ fn do_catch<F: FnOnce()>(_data: *mut u8, _payload: *mut u8) {}
+
+ let mut callback = Some(f);
+ let panicked = unsafe {
+ core::intrinsics::catch_unwind(
+ do_call::<F>,
+ (&mut callback as *mut Option<AssertUnwindSafe<F>>).cast(),
+ do_catch::<F>,
+ ) != 0
+ };
+
+ if panicked { Err(()) } else { Ok(()) }
+}
+
unsafe extern "C" fn new_thread_shim(
tcb: *mut Tcb,
synchronization_mutex: *const Mutex<u64>,
) -> ! {
- let tcb = unsafe { tcb.as_mut() }.expect_notls("non-null TLS is required");
+ let tcb = match unsafe { tcb.as_mut() } {
+ Some(tcb) => tcb,
+ None => {
+ log::error!("pthread: child thread started without a TCB");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ };
#[cfg(not(target_os = "redox"))]
{
@@ -227,12 +256,23 @@ unsafe extern "C" fn new_thread_shim(
unsafe {
tcb.activate(None);
}
- redox_rt::signal::setup_sighandler(&tcb.os_specific, false);
+ match catch_unwind(AssertUnwindSafe(|| {
+ redox_rt::signal::setup_sighandler(&tcb.os_specific, false)
+ })) {
+ Ok(()) => {}
+ Err(()) => {
+ log::error!("pthread: failed to set up child thread signal handler");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ }
}
let procmask = unsafe { (&*synchronization_mutex).as_ptr().read() };
- unsafe { tcb.copy_masters() }.unwrap();
+ if let Err(err) = unsafe { tcb.copy_masters() } {
+ log::error!("pthread: failed to copy TLS masters for child thread: {err:?}");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
unsafe { (*tcb).pthread.os_tid.get().write(Sys::current_os_tid()) };
@@ -240,11 +280,21 @@ unsafe extern "C" fn new_thread_shim(
#[cfg(target_os = "redox")]
{
- redox_rt::signal::set_sigmask(Some(procmask), None)
- .expect("failed to set procmask in child thread");
+ if let Err(err) = redox_rt::signal::set_sigmask(Some(procmask), None) {
+ log::error!("pthread: failed to set child thread signal mask: {err:?}");
+ }
}
- let retval = unsafe { entry_point(arg) };
+ let mut retval = ptr::null_mut();
+ match catch_unwind(AssertUnwindSafe(|| {
+ retval = unsafe { entry_point(arg) };
+ })) {
+ Ok(()) => {}
+ Err(()) => {
+ log::error!("pthread: child thread entry point panicked");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ }
unsafe { exit_current_thread(Retval(retval)) }
}
@@ -1,33 +0,0 @@
mod registers;
use std::env;
use std::process;
use log::{info, error, LevelFilter};
struct StderrLogger;
impl log::Log for StderrLogger {
fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info }
fn log(&self, r: &log::Record) { eprintln!("[{}] ehcid: {}", r.level(), r.args()); }
fn flush(&self) {}
}
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); }
};
let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default();
info!("EHCI USB 2.0 controller at {} (PCI fd: {})", device_path, channel_fd);
// Enable bus mastering and MMIO via the channel
let enable_cmd: [u8; 4] = [0x07, 0x00, 0x00, 0x00]; // IO + MEM + BUS_MASTER
if let Err(e) = syscall::write(channel_fd, &enable_cmd) {
error!("failed to enable device: {}", e);
}
info!("ehcid: initialized — ready for enumeration");
}
@@ -1,23 +0,0 @@
mod registers;
use std::env;
use std::process;
use log::{info, error, LevelFilter};
struct StderrLogger;
impl log::Log for StderrLogger {
fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info }
fn log(&self, r: &log::Record) { eprintln!("[{}] ohcid: {}", r.level(), r.args()); }
fn flush(&self) {}
}
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); }
};
info!("OHCI USB 1.1 controller (PCI fd: {})", channel_fd);
info!("ohcid: ready");
}
@@ -1,23 +0,0 @@
mod registers;
use std::env;
use std::process;
use log::{info, error, LevelFilter};
struct StderrLogger;
impl log::Log for StderrLogger {
fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info }
fn log(&self, r: &log::Record) { eprintln!("[{}] uhcid: {}", r.level(), r.args()); }
fn flush(&self) {}
}
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); }
};
info!("UHCI USB 1.1 controller (PCI fd: {})", channel_fd);
info!("uhcid: ready");
}
@@ -1,47 +0,0 @@
// D-Bus org.freedesktop.NetworkManager interface
// Exposes Wi-Fi device list, access points, connection state
// Uses zbus for D-Bus communication
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NmWifiDevice {
pub interface: String,
pub hw_address: String,
pub state: NmDeviceState,
pub access_points: Vec<NmAccessPoint>,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NmDeviceState {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IpConfig = 70,
IpCheck = 80,
Activated = 100,
Failed = 120,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NmAccessPoint {
pub ssid: String,
pub strength: u8,
pub security: String,
pub frequency: u32,
}
// Register D-Bus object path: /org/freedesktop/NetworkManager
// Properties: Devices, WirelessEnabled
// Methods: GetDevices, ActivateConnection, DeactivateConnection
pub fn register_nm_interface() {
#[cfg(feature = "dbus-nm")]
{
let _ = std::any::type_name::<zbus::Address>();
}
log::info!("wifictl: D-Bus NetworkManager interface registered");
}