fix: rewrite coretempd to use redox_scheme Socket + SchemeSync

Replaced broken UnixListener::bind(':coretemp') with proper redox_scheme::Socket::create() + SchemeSync trait impl. Event loop uses next_request/handle_sync/write_response pattern. Verified: registers scheme:coretemp, detects CPU info, zero panics.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-29 21:48:51 +03:00
parent daf131d435
commit 706050482b
2 changed files with 264 additions and 69 deletions
@@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
redox-scheme = "0.11"
redox_syscall = "0.7"
libredox = "0.1"
libc = "0.2"
[profile.release]
+263 -69
View File
@@ -1,10 +1,17 @@
use std::env;
use std::fs;
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::thread;
use std::time::Duration;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::scheme::SchemeState;
use redox_scheme::{CallerCtx, OpenResult, SignalBehavior, Socket};
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
use syscall::flag::O_ACCMODE;
use syscall::schemev2::NewFdFlags;
const POLL_MS: u64 = 2000;
const SCHEME_ROOT_ID: usize = 0;
const IA32_THERM_STATUS: u32 = 0x19c;
const IA32_TEMPERATURE_TARGET: u32 = 0x1a2;
@@ -19,7 +26,8 @@ enum Vendor {
fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr);
fs::read_to_string(&path).ok()
fs::read_to_string(&path)
.ok()
.and_then(|s| u64::from_str_radix(s.trim(), 16).ok())
}
@@ -39,27 +47,34 @@ fn detect_cpus() -> Vec<u32> {
for l in d.lines() {
if let Some(id_str) = l.strip_prefix("CPU ") {
if let Some((num, _)) = id_str.split_once(':') {
if let Ok(id) = num.trim().parse() { v.push(id); }
if let Ok(id) = num.trim().parse::<u32>() {
v.push(id);
}
}
}
}
}
if v.is_empty() { v.push(0); }
if v.is_empty() {
v.push(0);
}
v
}
fn read_temperature_intel(cpu: u32, tjmax: u8) -> Option<i16> {
let raw = read_msr(cpu, IA32_THERM_STATUS)?;
let digital_readout = ((raw >> 16) & 0x7F) as u8;
if digital_readout == 0 { return None; }
let temp = tjmax.saturating_sub(digital_readout);
Some(temp as i16)
if digital_readout == 0 {
return None;
}
Some(tjmax.saturating_sub(digital_readout) as i16)
}
fn read_tjmax_intel(cpu: u32) -> u8 {
if let Some(raw) = read_msr(cpu, IA32_TEMPERATURE_TARGET) {
let tj = ((raw >> 16) & 0xFF) as u8;
if tj > 0 && tj < 150 { return tj; }
if tj > 0 && tj < 150 {
return tj;
}
}
100
}
@@ -67,9 +82,10 @@ fn read_tjmax_intel(cpu: u32) -> u8 {
fn read_temperature_amd(cpu: u32) -> Option<i16> {
let raw = read_msr(cpu, AMD_TCTL)?;
let tctl = ((raw >> 21) & 0x3FF) as u16;
if tctl == 0 { return None; }
let temp = (tctl as f32) / 8.0;
Some(temp as i16)
if tctl == 0 {
return None;
}
Some(((tctl as f32) / 8.0) as i16)
}
#[derive(Clone)]
@@ -79,78 +95,256 @@ struct CpuInfo {
tjmax: u8,
}
fn main() {
let scheme_path = ":coretemp";
let _ = fs::remove_file(scheme_path);
let listener = UnixListener::bind(scheme_path).expect("bind scheme");
eprintln!("[INFO] coretempd: starting");
#[derive(Clone)]
enum Handle {
Listing,
CpuTemp { cpu_id: u32 },
}
let cpus = detect_cpus();
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
struct CoretempScheme {
cpus: Vec<CpuInfo>,
next_handle: usize,
handles: Vec<Option<Handle>>,
}
let cpu_infos: Vec<CpuInfo> = cpus.iter().map(|&id| {
let vendor = detect_vendor(id);
let tjmax = if vendor == Vendor::Intel {
read_tjmax_intel(id)
impl CoretempScheme {
fn new(cpus: Vec<CpuInfo>) -> Self {
Self {
cpus,
next_handle: 1,
handles: vec![None],
}
}
fn alloc_handle(&mut self, h: Handle) -> usize {
let id = self.next_handle;
self.handles.push(Some(h));
self.next_handle += 1;
id
}
fn get_handle(&self, id: usize) -> Result<&Handle> {
self.handles
.get(id)
.and_then(|opt| opt.as_ref())
.ok_or_else(|| Error::new(EBADF))
}
fn format_listing(&self) -> String {
let mut out = String::new();
for info in &self.cpus {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
match temp {
Some(t) => out.push_str(&format!("cpu{}: {}C\n", info.id, t)),
None => out.push_str(&format!("cpu{}: N/A\n", info.id)),
}
}
out
}
}
impl SchemeSync for CoretempScheme {
fn scheme_root(&mut self) -> Result<usize> {
Ok(SCHEME_ROOT_ID)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if flags & O_ACCMODE != 0 {
return Err(Error::new(EINVAL));
}
let handle = if dirfd == SCHEME_ROOT_ID {
let trimmed = path.trim_start_matches('/');
if trimmed.is_empty() {
Handle::Listing
} else if let Some(cpu_str) = trimmed.strip_prefix("cpu") {
let cpu_id = cpu_str
.trim_end_matches('/')
.parse::<u32>()
.map_err(|_| Error::new(ENOENT))?;
if self.cpus.iter().any(|c| c.id == cpu_id) {
Handle::CpuTemp { cpu_id }
} else {
return Err(Error::new(ENOENT));
}
} else {
return Err(Error::new(ENOENT));
}
} else {
0
return Err(Error::new(ENOENT));
};
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
CpuInfo { id, vendor, tjmax }
}).collect();
let infos_clone = cpu_infos.clone();
thread::spawn(move || {
loop {
thread::sleep(Duration::from_millis(POLL_MS));
for info in &infos_clone {
Ok(OpenResult::ThisScheme {
number: self.alloc_handle(handle),
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let data = match self.get_handle(id)? {
Handle::Listing => self.format_listing(),
Handle::CpuTemp { cpu_id } => {
let info = self
.cpus
.iter()
.find(|c| c.id == *cpu_id)
.ok_or_else(|| Error::new(ENOENT))?;
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", info.id), format!("{}\n", t));
match temp {
Some(t) => format!("{}\n", t),
None => "N/A\n".to_string(),
}
}
};
let bytes = data.as_bytes();
let off = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
if off >= bytes.len() {
return Ok(0);
}
let count = (bytes.len() - off).min(buf.len());
buf[..count].copy_from_slice(&bytes[off..off + count]);
Ok(count)
}
fn write(
&mut self,
_id: usize,
_buf: &[u8],
_offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
Err(Error::new(EINVAL))
}
fn on_close(&mut self, id: usize) {
if id < self.handles.len() {
self.handles[id] = None;
}
}
}
#[cfg(target_os = "redox")]
fn init_notify_fd() -> Option<i32> {
env::var("INIT_NOTIFY").ok()?.parse::<i32>().ok()
}
#[cfg(target_os = "redox")]
fn notify_scheme_ready(
notify_fd: i32,
socket: &Socket,
scheme: &mut CoretempScheme,
) -> Result<(), String> {
let cap_id = scheme
.scheme_root()
.map_err(|err| format!("coretempd: scheme_root failed: {err}"))?;
let cap_fd = socket
.create_this_scheme_fd(0, cap_id, 0, 0)
.map_err(|err| format!("coretempd: create_this_scheme_fd failed: {err}"))?;
syscall::call::write(
notify_fd as usize,
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
)
.map_err(|err| format!("coretempd: failed to notify init: {err}"))?;
Ok(())
}
#[cfg(target_os = "redox")]
fn run_daemon() -> Result<(), String> {
let notify_fd = init_notify_fd();
let cpus = detect_cpus();
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
let cpu_infos: Vec<CpuInfo> = cpus
.iter()
.map(|&id| {
let vendor = detect_vendor(id);
let tjmax = if vendor == Vendor::Intel {
read_tjmax_intel(id)
} else {
0
};
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
CpuInfo { id, vendor, tjmax }
})
.collect();
let socket = Socket::create()
.map_err(|err| format!("coretempd: failed to create scheme socket: {err}"))?;
let mut state = redox_scheme::scheme::SchemeState::new();
let mut scheme = CoretempScheme::new(cpu_infos);
if let Some(fd) = notify_fd {
notify_scheme_ready(fd, &socket, &mut scheme)?;
}
eprintln!("[INFO] coretempd: registered scheme:coretemp");
let infos_clone = scheme.cpus.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(POLL_MS));
for info in &infos_clone {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", info.id), format!("{}\n", t));
}
}
});
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let mut buf = [0u8; 64];
if let Ok(n) = stream.read(&mut buf) {
let req = String::from_utf8_lossy(&buf[..n]).trim().to_string();
let resp = if req == "/" {
let mut names = String::new();
for info in &cpu_infos {
names.push_str(&format!("cpu{}\n", info.id));
}
names
} else if let Some(cpu_str) = req.strip_prefix("/cpu") {
if let Ok(cpu) = cpu_str.parse::<u32>() {
if let Some(info) = cpu_infos.iter().find(|i| i.id == cpu) {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
format!("{}\n", t)
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
};
let _ = stream.write_all(resp.as_bytes());
}
loop {
let request = socket
.next_request(SignalBehavior::Restart)
.map_err(|err| format!("coretempd: failed to read scheme request: {err}"))?;
let Some(request) = request else {
return Ok(());
};
if let redox_scheme::RequestKind::Call(request) = request.kind() {
let response = request.handle_sync(&mut scheme, &mut state);
socket
.write_response(response, SignalBehavior::Restart)
.map_err(|err| format!("coretempd: failed to write response: {err}"))?;
}
}
}
#[cfg(not(target_os = "redox"))]
fn run_daemon() -> Result<(), String> {
eprintln!("[INFO] coretempd: not running on Redox, exiting");
Ok(())
}
fn main() {
if let Err(e) = run_daemon() {
eprintln!("[ERROR] coretempd: {e}");
std::process::exit(1);
}
}