init: Allow init to register the scheme for a daemon

Unfortunately the scheme still has to be created by the daemon due to a
kernel bug, but once that is fixed, it would allow delayed spawning of
scheme daemons.
This commit is contained in:
bjorn3
2026-02-18 22:29:56 +01:00
parent 7dad7af1f0
commit 7ebeceb41d
16 changed files with 190 additions and 74 deletions
Generated
+3
View File
@@ -517,6 +517,9 @@ name = "daemon"
version = "0.0.0"
dependencies = [
"libc",
"libredox",
"redox-scheme",
"redox_syscall 0.7.1",
]
[[package]]
+3
View File
@@ -5,3 +5,6 @@ edition = "2024"
[dependencies]
libc.workspace = true
libredox.workspace = true
redox-scheme.workspace = true
redox_syscall.workspace = true
+120 -7
View File
@@ -1,19 +1,32 @@
#![feature(never_type)]
use std::io::{self, PipeWriter, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use std::process::Command;
use libredox::Fd;
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();
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
panic!(
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
io::Error::last_os_error()
);
}
fd
}
#[must_use = "Daemon::ready must be called"]
pub struct Daemon {
write_pipe: PipeWriter,
}
impl Daemon {
pub fn new<F: FnOnce(Daemon) -> !>(f: F) -> ! {
let write_pipe = unsafe {
io::PipeWriter::from_raw_fd(std::env::var("INIT_NOTIFY").unwrap().parse().unwrap())
};
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
f(Daemon { write_pipe })
}
@@ -25,10 +38,10 @@ impl Daemon {
pub fn spawn(mut cmd: Command) {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
// Pass pipe to child
// Pass notify pipe to child
if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 {
eprintln!(
"daemon: failed to unset CLOEXEC flag for pipe: {}",
"daemon: failed to unset CLOEXEC flag for notify pipe: {}",
io::Error::last_os_error()
);
return;
@@ -57,3 +70,103 @@ impl Daemon {
}
}
}
#[must_use = "SchemeDaemon::ready must be called"]
pub struct SchemeDaemon {
write_pipe: PipeWriter,
}
impl SchemeDaemon {
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
f(SchemeDaemon { write_pipe })
}
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
syscall::call_wo(
self.write_pipe.as_raw_fd() as usize,
&cap_fd.into_raw().to_ne_bytes(),
syscall::CallFlags::FD,
&[],
)?;
Ok(())
}
pub fn ready_sync_scheme<S: SchemeSync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
pub fn ready_async_scheme<S: SchemeAsync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
pub fn spawn(mut cmd: Command, scheme_name: &str) {
let (read_pipe, write_pipe) = io::pipe().unwrap();
// Pass notify pipe to child
if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 {
eprintln!(
"daemon: failed to unset CLOEXEC flag for notify pipe: {}",
io::Error::last_os_error()
);
return;
}
cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd()));
if let Err(err) = cmd.spawn() {
eprintln!("daemon: failed to execute {cmd:?}: {err}");
return;
}
drop(write_pipe);
let mut new_fd = usize::MAX;
let fd_bytes = unsafe {
core::slice::from_raw_parts_mut(
core::slice::from_mut(&mut new_fd).as_mut_ptr() as *mut u8,
core::mem::size_of::<usize>(),
)
};
loop {
match syscall::call_ro(
read_pipe.as_raw_fd() as usize,
fd_bytes,
syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER,
&[],
) {
Err(syscall::Error {
errno: syscall::EINTR,
}) => continue,
Ok(0) => {
eprintln!("daemon: {cmd:?} exited without notifying readiness");
return;
}
Ok(1) => break,
Ok(n) => {
eprintln!("daemon: incorrect amount of fds {n} returned");
return;
}
Err(err) => {
eprintln!("daemon: failed to wait for {cmd:?}: {err}");
return;
}
}
}
let current_namespace_fd = libredox::call::getns().expect("TODO");
libredox::call::register_scheme_to_ns(current_namespace_fd, scheme_name, new_fd)
.expect("TODO");
}
}
+5 -9
View File
@@ -7,23 +7,22 @@
//! In the future fbbootlogd may also pull from logd as opposed to have logd push logs to it. And it
//! it could display a boot splash like plymouth instead of a boot log when booting in quiet mode.
use std::io::Write;
use std::os::fd::AsRawFd;
use event::EventQueue;
use inputd::ConsumerHandleEvent;
use libredox::errno::EAGAIN;
use orbclient::Event;
use redox_scheme::{scheme::register_sync_scheme, RequestKind, SignalBehavior, Socket};
use redox_scheme::{RequestKind, SignalBehavior, Socket};
use crate::scheme::FbbootlogScheme;
mod scheme;
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue");
event::user_data! {
@@ -58,7 +57,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
.create_this_scheme_fd(0, 0, 0, 0)
.expect("fbbootlogd: failed to create log fd");
// Add ourself as log sink
let mut log_file = libredox::Fd::open(
let log_file = libredox::Fd::open(
"/scheme/log/add_sink",
libredox::flag::O_WRONLY | libredox::flag::O_CLOEXEC,
0,
@@ -69,15 +68,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
.expect("fbbootlogd: failed to send log fd to log scheme.");
}
register_sync_scheme(&socket, "fbbootlog", &mut scheme)
.expect("fbbootlog: failed to register scheme to namespace");
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
// This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics
// driver handoff. In the future inputd may directly pass a handle to the display instead.
//libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace");
daemon.ready();
for event in event_queue {
match event.expect("fbbootlogd: failed to get event").user_data {
Source::Scheme => {
+5 -8
View File
@@ -3,7 +3,7 @@ use inputd::ConsumerHandleEvent;
use libredox::errno::{EAGAIN, EINTR};
use orbclient::Event;
use redox_scheme::{
scheme::{register_sync_scheme, Op, SchemeResponse, SchemeSync},
scheme::{Op, SchemeResponse, SchemeSync},
CallerCtx, RequestKind, Response, SignalBehavior, Socket,
};
use std::env;
@@ -16,9 +16,9 @@ mod scheme;
mod text;
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let vt_ids = env::args()
.skip(1)
.map(|arg| arg.parse().expect("invalid vt number"))
@@ -46,15 +46,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue);
register_sync_scheme(&socket, "fbcon", &mut scheme)
.expect("fbcond: failed to register scheme to namespace");
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
// This is not possible for now as fbcond needs to open new displays at runtime for graphics
// driver handoff. In the future inputd may directly pass a handle to the display instead.
// libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace");
daemon.ready();
let mut blocked = Vec::new();
// Handle all events that could have happened before registering with the event queue.
@@ -139,7 +136,7 @@ fn handle_event(
RequestKind::Cancellation(cancellation_request) => {
if let Some(i) = blocked
.iter()
.position(|(op, caller)| caller.id == cancellation_request.id)
.position(|(_op, caller)| caller.id == cancellation_request.id)
{
let (blocked_req, _) = blocked.remove(i);
let resp = Response::err(EINTR, blocked_req);
+5 -6
View File
@@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use inputd::{ControlEvent, VtEvent, VtEventKind};
use libredox::errno::ESTALE;
use redox_scheme::scheme::{register_sync_scheme, SchemeSync};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket};
use orbclient::{Event, EventOption};
@@ -552,13 +552,12 @@ impl SchemeSync for InputScheme {
}
}
fn deamon(deamon: daemon::Daemon) -> anyhow::Result<()> {
fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> {
// Create the ":input" scheme.
let socket_file = Socket::create()?;
let mut scheme = InputScheme::new();
register_sync_scheme(&socket_file, "input", &mut scheme)?;
deamon.ready();
let _ = daemon.ready_sync_scheme(&socket_file, &mut scheme);
loop {
scheme.has_new_events = false;
@@ -632,7 +631,7 @@ fn deamon(deamon: daemon::Daemon) -> anyhow::Result<()> {
}
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
deamon(daemon).unwrap();
unreachable!();
}
@@ -697,6 +696,6 @@ fn main() {
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
daemon::SchemeDaemon::new(daemon_runner);
}
}
+3 -3
View File
@@ -4,6 +4,6 @@ export PATH /scheme/initfs/bin
export LD_LIBRARY_PATH /scheme/initfs/lib
export RUST_BACKTRACE 1
rtcd
notify nulld
notify zerod
notify randd
scheme null nulld
scheme zero zerod
scheme rand randd
+2 -2
View File
@@ -1,3 +1,3 @@
notify logd
scheme log logd
stdio /scheme/log
notify ramfs logging
scheme logging ramfs logging
+3 -3
View File
@@ -1,8 +1,8 @@
notify inputd
scheme input inputd
notify vesad
unset FRAMEBUFFER_ADDR FRAMEBUFFER_VIRT FRAMEBUFFER_WIDTH FRAMEBUFFER_HEIGHT FRAMEBUFFER_STRIDE
#TODO: unset FRAMEBUFFER1 and beyond?
notify fbbootlogd
scheme fbbootlog fbbootlogd
# Activate framebuffer log VT, which disables kernel graphical debug
inputd -A 1
notify fbcond 2
scheme fbcon fbcond 2
+13
View File
@@ -124,6 +124,19 @@ fn run_command(cmd: Command, config: &InitConfig) {
daemon::Daemon::spawn(command);
}
Command::Scheme(scheme, cmd, args) => {
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{} {}'", cmd, args.join(" "));
return;
}
let mut command = process::Command::new(&cmd);
for arg in args {
command.arg(arg);
}
daemon::SchemeDaemon::spawn(command, &scheme);
}
Command::Regular(cmd, args) => {
let mut command = process::Command::new(cmd.clone());
for arg in args {
+11
View File
@@ -37,6 +37,7 @@ pub enum Command {
// Service
Nowait(String, Vec<String>),
Notify(String, Vec<String>),
Scheme(String, String, Vec<String>),
Regular(String, Vec<String>),
// Modify env
@@ -110,6 +111,16 @@ impl Command {
Ok(Command::Notify(cmd, args.collect()))
}
"scheme" => {
let Some(scheme) = args.next() else {
return Err("init: failed to run scheme: no argument".to_owned());
};
let Some(cmd) = args.next() else {
return Err("init: failed to run scheme: missing command".to_owned());
};
Ok(Command::Scheme(scheme, cmd, args.collect()))
}
_ => Ok(Command::Regular(cmd, args.collect())),
}
}
+4 -10
View File
@@ -1,25 +1,19 @@
use redox_scheme::{
scheme::{register_sync_scheme, SchemeSync},
RequestKind, Response, SignalBehavior, Socket,
};
use redox_scheme::{scheme::SchemeSync, RequestKind, Response, SignalBehavior, Socket};
use std::process;
use crate::scheme::LogScheme;
mod scheme;
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let socket = Socket::create().expect("logd: failed to create log scheme");
let mut scheme = LogScheme::new(&socket);
register_sync_scheme(&socket, "log", &mut scheme)
.expect("logd: failed to register scheme to namespace");
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
libredox::call::setrens(0, 0).expect("logd: failed to enter null namespace");
daemon.ready();
while let Some(request) = socket
.next_request(SignalBehavior::Restart)
.expect("logd: failed to read events from log scheme")
@@ -50,5 +44,5 @@ fn daemon(daemon: daemon::Daemon) -> ! {
}
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
-1
View File
@@ -3,7 +3,6 @@ use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::mem;
use std::os::fd::{FromRawFd, RawFd};
use std::path::PathBuf;
use std::sync::mpsc::{self, Sender};
use redox_scheme::scheme::SchemeSync;
+4 -5
View File
@@ -8,19 +8,18 @@ use redox_scheme::{RequestKind, SignalBehavior};
use self::scheme::Scheme;
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let scheme_name = env::args().nth(1).expect("Usage:\n\tramfs SCHEME_NAME");
let socket = redox_scheme::Socket::create().expect("ramfs: failed to create socket");
let mut scheme = Scheme::new(scheme_name.clone()).expect("ramfs: failed to initialize scheme");
redox_scheme::scheme::register_sync_scheme(&socket, &scheme_name, &mut scheme)
.expect("ramfs: failed to register to namespace");
daemon.ready();
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
libredox::call::setrens(0, 0).expect("ramfs: failed to enter null namespace");
loop {
+5 -9
View File
@@ -14,13 +14,12 @@ pub const MODE_READ: u16 = 0o4;
use raw_cpuid::CpuId;
use redox_scheme::{
scheme::{register_sync_scheme, SchemeSync},
CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket,
scheme::SchemeSync, CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket,
};
use syscall::data::Stat;
use syscall::flag::{EventFlags, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_WRONLY};
use syscall::schemev2::NewFdFlags;
use syscall::{Error, Result, EACCES, EBADF, EEXIST, ENOENT, EOPNOTSUPP, EPERM, MODE_CHR};
use syscall::{Error, Result, EACCES, EBADF, EEXIST, ENOENT, EPERM, MODE_CHR};
// Create an RNG Seed to create initial seed from the rdrand intel instruction
use rand_core::SeedableRng;
@@ -465,15 +464,12 @@ impl SchemeSync for RandScheme {
}
}
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let socket = Socket::create().expect("randd: failed to create rand scheme");
let mut scheme = RandScheme::new();
register_sync_scheme(&socket, "rand", &mut scheme)
.expect("randd: failed to register scheme to namespace");
daemon.ready();
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
libredox::call::setrens(0, 0).expect("randd: failed to enter null namespace");
@@ -497,5 +493,5 @@ fn daemon(daemon: daemon::Daemon) -> ! {
}
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
+4 -11
View File
@@ -1,4 +1,4 @@
use redox_scheme::{scheme::register_sync_scheme, RequestKind, SignalBehavior, Socket};
use redox_scheme::{RequestKind, SignalBehavior, Socket};
use scheme::ZeroScheme;
@@ -10,27 +10,20 @@ enum Ty {
}
fn main() {
daemon::Daemon::new(daemon);
daemon::SchemeDaemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let ty = match &*std::env::args().next().unwrap() {
"nulld" => Ty::Null,
"zerod" => Ty::Zero,
_ => panic!("needs to be called as either nulld or zerod"),
};
let name = match ty {
Ty::Null => "null",
Ty::Zero => "zero",
};
let socket = Socket::create().expect("zerod: failed to create zero scheme");
let mut zero_scheme = ZeroScheme(ty);
register_sync_scheme(&socket, name, &mut zero_scheme)
.expect("zerod: failed to register scheme to namespace");
daemon.ready();
let _ = daemon.ready_sync_scheme(&socket, &mut zero_scheme);
libredox::call::setrens(0, 0).expect("zerod: failed to enter null namespace");