diff --git a/local/AGENTS.md b/local/AGENTS.md index 5c2f8983e7..cd2b779ead 100644 --- a/local/AGENTS.md +++ b/local/AGENTS.md @@ -1855,3 +1855,67 @@ Config comparison: ## ANTI-PATTERNS (COMMIT POLICY) - **DO NOT** include AI attribution in commit messages — no "Ultraworked with [Sisyphus]", "Co-authored-by: Sisyphus", or similar AI agent footers. Commits belong to the human author only. + +## LIBRARY-ONLY RED BEAR CRATES + +The following Red Bear crates exist as **library-only dependencies** — they provide shared +types, protocols, or utility code consumed by other Red Bear daemons and binaries. They are +**not recipes in any config TOML**, have no init service, and are never cooked as standalone +packages by the build system. They are consumed exclusively via Cargo `path` dependencies. + +| Crate | Location | Purpose | Consumers | +|-------|----------|---------|-----------| +| `redbear-hid-core` | `local/recipes/system/redbear-hid-core/source/` | Shared HID protocol types, report descriptors, and device classification enums | `redbear-inputd`, `redbear-bthid`, future HID daemons | +| `redbear-login-protocol` | `local/recipes/system/redbear-login-protocol/source/` | Shared types and wire format for the greeter-auth-session handshake: login request/response, session token, PAM conversation messages | `redbear-greeter`, `redbear-authd`, `redbear-session-launch` | +| `redbear-tui-theme` | `local/recipes/system/redbear-tui-theme/source/` | (When wired as lib): shared ratatui color palettes, text styles, and layout constants for consistent TUI theming | `cub`, `tlc`, `redbear-power`, other ratatui-based TUI tools | +| `redbear-passwd` | `local/recipes/system/redbear-passwd/source/` | `passwd` file parsing, user/group lookup, shadow entry validation, and credential verification types | `redbear-authd`, `redbear-polkit`, `redbear-greeter`, `redbear-session-launch` | + +### Consumption Pattern + +These crates are used via **path dependencies** in other Red Bear daemon Cargo.toml files: + +```toml +[dependencies] +redbear-hid-core = { path = "../../../system/redbear-hid-core/source" } +redbear-login-protocol = { path = "../../../system/redbear-login-protocol/source" } +redbear-passwd = { path = "../../../system/redbear-passwd/source" } +``` + +The path is relative to the consuming crate's `Cargo.toml` location. + +### Why Library-Only + +These crates are **intentionally excluded from all config TOMLs** (`redbear-full.toml`, +`redbear-mini.toml`, `redbear-grub.toml`) and have no `[package]` / `installs` in their +recipe. Reasons: + +1. **No standalone binary** — They produce only a `lib` target, never a `bin`. +2. **No init service** — They are not daemons and do not register a scheme or D-Bus name. +3. **Config presence would be misleading** — Including them as a package suggests they + contribute runtime artifacts to the image, which they do not. +4. **Linking is handled by the consumer** — The consumer daemon's `Cargo.toml` pulls them + in; the build system compiles them transitively during the consumer's `repo cook`. + +### Host Testing + +Since these are pure-logic crates with no Redox-specific syscalls (no `libredox`, no +`redox_syscall` dep, no scheme I/O), their unit tests are **host-runnable**: + +```bash +cargo test --manifest-path local/recipes/system/redbear-hid-core/source/Cargo.toml +cargo test --manifest-path local/recipes/system/redbear-login-protocol/source/Cargo.toml +cargo test --manifest-path local/recipes/system/redbear-passwd/source/Cargo.toml +``` + +They are included in the CI `cargo test host` step. + +### Finding All Library-Only Crates + +```bash +# List crates not in any config TOML +for crate in redbear-hid-core redbear-login-protocol redbear-passwd; do + if ! grep -rq "\"$crate\"" config/redbear-*.toml; then + echo "$crate: library-only (not in config)" + fi +done +``` diff --git a/local/recipes/drivers/redbear-iwlwifi/source/Cargo.toml b/local/recipes/drivers/redbear-iwlwifi/source/Cargo.toml index 731018a3af..f73bc9f384 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/Cargo.toml +++ b/local/recipes/drivers/redbear-iwlwifi/source/Cargo.toml @@ -21,6 +21,7 @@ redox-driver-sys = { path = "../../redox-driver-sys/source", features = ["redox" pcid_interface = { path = "../../../../sources/base/drivers/pcid", package = "pcid" } libredox = { path = "../../../../../local/sources/libredox" } syscall = { package = "redox_syscall", path = "../../../../../local/sources/syscall", features = ["std"] } +redox-scheme = { path = "../../../../../local/sources/redox-scheme" } [patch.crates-io] redox-driver-sys = { path = "../../redox-driver-sys/source" } diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/callback.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/callback.rs index b80faaf2ae..d469cb3474 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/callback.rs +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/callback.rs @@ -44,7 +44,7 @@ pub struct SkBuff { } // Forward declaration of kfree_skb from linux-kpi -extern "C" { +unsafe extern "C" { fn kfree_skb(skb: *mut SkBuff); } diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/scheme.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/scheme.rs index f9eba14591..b06bf4cc9f 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/scheme.rs +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/bridge/scheme.rs @@ -35,7 +35,9 @@ struct HandleMap { impl HandleMap { fn new() -> Self { - Self { next: 0, map: std::collections::HashMap::new() } + // Start handle ids at 1; 0 is avoided so a zero-initialised id is never + // mistaken for a valid handle. + Self { next: 1, map: std::collections::HashMap::new() } } fn insert(&mut self, kind: HandleKind) -> usize { @@ -77,158 +79,135 @@ pub fn run_event_loop( } // —— Redox implementation —————————————————————————————————————— +// +// Ported from the legacy redox_syscall Packet-based scheme to the current +// redox-scheme SchemeSync API. The legacy `syscall::Packet` / `syscall::open` +// / `SYS_OPEN` scheme-server ABI no longer exists; this mirrors the canonical +// NIC scheme (base drivers/net/driver-network) so smolnetd's `network.*` +// consumer works unchanged: +// * the scheme root is a directory; +// * opening "" yields a Data handle for raw Ethernet frame read/write; +// * opening "mac" yields a 6-byte, positioned MAC-address handle. +// Read on Data pops one RX frame (EAGAIN when empty on a non-blocking handle); +// write on Data converts Ethernet -> 802.11 and submits via the C TX path. +// Blocked readers are woken with post_fevent(EVENT_READ) whenever RX frames +// become available. The daemon uses a non-blocking socket and a 1 ms poll loop +// (matching the legacy behaviour) so it can also pump the bridge TX path and +// post readiness without a dedicated event fd. #[cfg(target_os = "redox")] mod inner { - use super::*; - use libredox::flag::{EVENT_READ, O_NONBLOCK}; - use std::cmp; + use super::{ethernet_to_wifi, HandleKind, HandleMap, WifiLinkBridge}; use std::io; - use syscall::flag; - use syscall::{ - Error, Result as SysResult, EBADF, EAGAIN, EINVAL, EWOULDBLOCK, ERANGE, MODE_FILE, Packet, - }; + use std::sync::{Arc, Mutex}; - pub struct SchemeInner { + use redox_scheme::scheme::{register_sync_scheme, SchemeState, SchemeSync}; + use redox_scheme::{ + CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket, + }; + use syscall::error::{ + Error, Result as SysResult, EACCES, EAGAIN, EBADF, EINVAL, EIO, EWOULDBLOCK, + }; + use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_NONBLOCK}; + use syscall::schemev2::NewFdFlags; + use syscall::Stat; + + // FFI: TX submission — called to send a Wi-Fi frame down the C driver path. + unsafe extern "C" { + fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32; + } + + pub struct WlanScheme { name: String, handles: HandleMap, - fd: usize, bridge: Arc>, mac: [u8; 6], bssid: [u8; 6], } - const SYS_OPEN: usize = syscall::number::SYS_OPEN; - const SYS_READ: usize = syscall::number::SYS_READ; - const SYS_WRITE: usize = syscall::number::SYS_WRITE; - const SYS_FPATH: usize = syscall::number::SYS_FPATH; - const SYS_FSTAT: usize = syscall::number::SYS_FSTAT; - const SYS_FSYNC: usize = syscall::number::SYS_FSYNC; - const SYS_CLOSE: usize = syscall::number::SYS_CLOSE; - const SYS_FEVENT: usize = syscall::number::SYS_FEVENT; - - impl SchemeInner { + impl WlanScheme { pub fn new( name: &str, bridge: Arc>, mac: [u8; 6], bssid: [u8; 6], - ) -> io::Result { - let path = format!(":{}", name); - let fd = syscall::open(&path, flag::O_RDWR | flag::O_NONBLOCK | flag::O_CREAT) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("scheme socket {}: {:?}", path, e)))?; - - log::info!("bridge::scheme: registered {}", name); - - Ok(Self { + ) -> Self { + Self { name: name.to_string(), handles: HandleMap::new(), - fd, bridge, mac, bssid, - }) - } - - pub fn fd(&self) -> usize { self.fd } - - pub fn tick(&mut self) -> io::Result { - let mut pkt = Packet::default(); - match syscall::read(self.fd, &mut pkt) { - Ok(_) => { self.dispatch(pkt)?; Ok(true) } - Err(err) if err.errno == syscall::EAGAIN => Ok(false), - Err(err) => Err(io::Error::from_raw_os_error(err.errno)), } } - pub fn notify_readers(&self) { - for id in self.handles.data_handles() { - let p = Packet { - id: 0, - a: SYS_FEVENT, - b: id, - c: EVENT_READ.bits(), - d: 0, - }; - let _ = syscall::write(self.fd, &p); - } + fn kind(&self, id: usize) -> SysResult<&HandleKind> { + self.handles.get(id).ok_or(Error::new(EBADF)) } - fn dispatch(&mut self, pkt: Packet) -> io::Result<()> { - let result = match pkt.a { - SYS_OPEN => self.do_open(&pkt), - SYS_READ => self.do_read(&pkt), - SYS_WRITE => self.do_write(&pkt), - SYS_FPATH => self.do_fpath(&pkt), - SYS_FSTAT => self.do_fstat(&pkt), - SYS_FSYNC => self.do_fsync(&pkt), - SYS_CLOSE => { self.handles.remove(pkt.b); Ok(None) } - _ => Err(Error::new(syscall::ENOSYS)), + /// Ids of all currently-open Data handles, for readiness notification. + pub fn data_handle_ids(&self) -> Vec { + self.handles.data_handles() + } + } + + impl SchemeSync for WlanScheme { + fn scheme_root(&mut self) -> SysResult { + Ok(self.handles.insert(HandleKind::Root)) + } + + fn openat( + &mut self, + dirfd: usize, + path: &str, + _flags: usize, + _fcntl_flags: u32, + ctx: &CallerCtx, + ) -> SysResult { + if !matches!(self.kind(dirfd)?, HandleKind::Root) { + return Err(Error::new(EACCES)); + } + if ctx.uid != 0 { + return Err(Error::new(EACCES)); + } + let (kind, flags) = match path.trim_matches('/') { + "" => (HandleKind::Data, NewFdFlags::empty()), + "mac" => (HandleKind::Mac, NewFdFlags::POSITIONED), + _ => return Err(Error::new(EINVAL)), }; - - let id = pkt.id; - match result { - Ok(Some(mut r)) => { r.id = id; syscall::write(self.fd, &r).ok(); } - Ok(None) => {} - Err(err) => { - let mut r = Packet::default(); - r.id = id; - r.a = err.errno; - if err.errno != EAGAIN && err.errno != EWOULDBLOCK { - syscall::write(self.fd, &r).ok(); - } - } - } - Ok(()) + let number = self.handles.insert(kind); + Ok(OpenResult::ThisScheme { number, flags }) } - fn do_open(&mut self, _pkt: &Packet) -> SysResult> { - let id = self.handles.insert(HandleKind::Data); - let mut r = Packet::default(); - r.a = 0; - r.b = id; - Ok(Some(r)) - } - - fn do_read(&mut self, pkt: &Packet) -> SysResult> { - let hid = pkt.b; - let max = pkt.c; - let fl = pkt.d as u32; - let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?; - - match kind { + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> SysResult { + match self.kind(id)? { HandleKind::Mac => { - let off = pkt.d as usize; + let off = offset as usize; let mac = self.mac; - if off >= 6 { - return Err(Error::new(ERANGE)); + if off >= mac.len() { + return Ok(0); } - let n = cmp::min(max, 6 - off); - let mut r = Packet::default(); - r.a = 0; - r.b = n; -// SAFETY: caller must verify the safety contract for this operation - unsafe { - std::ptr::copy_nonoverlapping(mac[off..].as_ptr(), &mut r.c as *mut usize as *mut u8, n); - } - Ok(Some(r)) + let n = core::cmp::min(buf.len(), mac.len() - off); + buf[..n].copy_from_slice(&mac[off..off + n]); + Ok(n) } HandleKind::Data => { - let mut bridge = self.bridge.lock().unwrap(); - match bridge.pop_rx() { + let frame = self.bridge.lock().unwrap().pop_rx(); + match frame { Some(frame) => { - let n = cmp::min(frame.len(), max); - let mut r = Packet::default(); - r.a = 0; - r.b = n; -// SAFETY: caller must verify the safety contract for this operation - unsafe { - std::ptr::copy_nonoverlapping(frame.as_ptr(), &mut r.c as *mut usize as *mut u8, n); - } - Ok(Some(r)) + let n = core::cmp::min(frame.len(), buf.len()); + buf[..n].copy_from_slice(&frame[..n]); + Ok(n) } None => { - if fl & O_NONBLOCK as u32 != 0 { + if fcntl_flags & O_NONBLOCK as u32 != 0 { Err(Error::new(EAGAIN)) } else { Err(Error::new(EWOULDBLOCK)) @@ -236,84 +215,180 @@ mod inner { } } } - _ => Err(Error::new(EBADF)), + HandleKind::Root => Err(Error::new(EBADF)), } } - fn do_write(&mut self, pkt: &Packet) -> SysResult> { - let hid = pkt.b; - let len = pkt.c; - let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?; - - if !matches!(kind, HandleKind::Data) { + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> SysResult { + if !matches!(self.kind(id)?, HandleKind::Data) { return Err(Error::new(EINVAL)); } - - // Extract data from the packet's inline area - let mut buf = vec![0u8; cmp::min(len, std::mem::size_of::() * 4)]; - unsafe { - let src = &pkt.c as *const usize as *const u8; - std::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), buf.len()); - } - buf.truncate(len); - - // Convert Ethernet → 802.11 and submit let bssid = self.bssid; let mac = self.mac; - if let Some(wifi) = ethernet_to_wifi(&buf, &bssid, &mac) { - // Try submitting via C TX path - let submitted = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) }; - if submitted != 0 { - self.bridge.lock().unwrap().stats.inc_tx_dropped(); - return Err(Error::new(syscall::EIO)); + match ethernet_to_wifi(buf, &bssid, &mac) { + Some(wifi) => { + // SAFETY: `wifi` is a live, contiguous byte buffer; the C + // side only reads `len` bytes from `data` and does not + // retain the pointer past the call. + let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) }; + if rc != 0 { + self.bridge.lock().unwrap().stats.inc_tx_dropped(); + return Err(Error::new(EIO)); + } + self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len()); + Ok(buf.len()) } - self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len()); + None => { + self.bridge.lock().unwrap().stats.inc_tx_dropped(); + Err(Error::new(EINVAL)) + } + } + } - let mut r = Packet::default(); - r.a = 0; - r.b = len; - Ok(Some(r)) + fn fevent( + &mut self, + id: usize, + _flags: EventFlags, + _ctx: &CallerCtx, + ) -> SysResult { + let _ = self.kind(id)?; + Ok(EventFlags::empty()) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> SysResult { + let leaf = match self.kind(id)? { + HandleKind::Root | HandleKind::Data => "", + HandleKind::Mac => "mac", + }; + let path = if leaf.is_empty() { + format!("{}:", self.name) } else { - self.bridge.lock().unwrap().stats.inc_tx_dropped(); - Err(Error::new(EINVAL)) - } + format!("{}:{}", self.name, leaf) + }; + let bytes = path.as_bytes(); + let n = core::cmp::min(buf.len(), bytes.len()); + buf[..n].copy_from_slice(&bytes[..n]); + Ok(n) } - fn do_fpath(&mut self, _pkt: &Packet) -> SysResult> // SAFETY: caller must verify the safety contract for this operation -{ - let name = self.name.clone(); - let mut r = Packet::default(); - r.a = 0; - r.b = name.len(); - unsafe { - std::ptr::copy_nonoverlapping(name.as_ptr(), &mut r.c as *mut usize as *mut u8, name.len()); + fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SysResult<()> { + match self.kind(id)? { + HandleKind::Root => { + stat.st_mode = MODE_DIR | 0o500; + } + HandleKind::Data => { + stat.st_mode = MODE_FILE | 0o700; + } + HandleKind::Mac => { + stat.st_mode = MODE_FILE | 0o400; + stat.st_size = 6; + } } - Ok(Some(r)) + Ok(()) } - fn do_fstat(&mut self, pkt: &Packet) -> SysResult> { - let hid = pkt.b; - let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?; - let mut r = Packet::default(); - r.a = 0; - match kind { - HandleKind::Data => { r.c = MODE_FILE | 0o700; } - HandleKind::Mac => { r.c = MODE_FILE | 0o400; r.d = 6; } - HandleKind::Root => { r.c = MODE_FILE | 0o500; } - } - Ok(Some(r)) + fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> SysResult<()> { + let _ = self.kind(id)?; + Ok(()) } - fn do_fsync(&mut self, _pkt: &Packet) -> SysResult> { - let mut r = Packet::default(); - r.a = 0; - Ok(Some(r)) + fn on_close(&mut self, id: usize) { + self.handles.remove(id); } } - // FFI: TX submission — called by bridge to send a Wi-Fi frame - extern "C" { - fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32; + /// Register the `network.` scheme and service it until the socket + /// closes. Non-blocking: also pumps the bridge TX path and posts read + /// readiness on each poll tick. + pub fn run( + scheme_name: &str, + bridge: Arc>, + mac: [u8; 6], + bssid: [u8; 6], + ) -> io::Result<()> { + let socket = Socket::nonblock().map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("create scheme socket: {e}")) + })?; + let mut scheme = WlanScheme::new(scheme_name, Arc::clone(&bridge), mac, bssid); + register_sync_scheme(&socket, scheme_name, &mut scheme).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("register scheme {scheme_name}: {e}"), + ) + })?; + let mut state = SchemeState::new(); + + log::info!("bridge::scheme: registered {} (redox-scheme)", scheme_name); + + let read_flag = EventFlags::EVENT_READ.bits(); + loop { + // Drain all currently-pending scheme requests. + loop { + match socket.next_request(SignalBehavior::Restart) { + Ok(Some(request)) => match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme, &mut state); + if let Err(e) = + socket.write_response(response, SignalBehavior::Restart) + { + log::error!("bridge::scheme: write_response: {e}"); + } + } + RequestKind::OnClose { id } => scheme.on_close(id), + _ => {} + }, + Ok(None) => { + log::info!("bridge::scheme: socket closed, exiting"); + return Ok(()); + } + Err(e) if e.errno == EAGAIN => break, + Err(e) => { + log::error!("bridge::scheme: next_request: {e}"); + break; + } + } + } + + // Pump a pending Ethernet TX frame (submit_tx path) to the C driver. + let tx_frame = bridge.lock().unwrap().take_tx(); + if let Some(eth) = tx_frame { + let (bssid_copy, mac_copy) = { + let b = bridge.lock().unwrap(); + (b.bssid, b.mac) + }; + if let Some(wifi) = ethernet_to_wifi(ð, &bssid_copy, &mac_copy) { + // SAFETY: `wifi` is live for the duration of the call; the C + // side copies out `len` bytes and does not retain the pointer. + let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) }; + if rc != 0 { + bridge.lock().unwrap().stats.inc_tx_dropped(); + } else { + bridge.lock().unwrap().stats.inc_tx_frame(eth.len()); + } + } else { + bridge.lock().unwrap().stats.inc_tx_dropped(); + } + } + + // Wake blocked readers when RX frames are available. + if bridge.lock().unwrap().available_for_read() > 0 { + for id in scheme.data_handle_ids() { + let _ = socket.write_response( + Response::post_fevent(id, read_flag), + SignalBehavior::Restart, + ); + } + } + + std::thread::sleep(std::time::Duration::from_millis(1)); + } } } @@ -326,62 +401,16 @@ pub fn run_event_loop( mac: [u8; 6], bssid: [u8; 6], ) -> ! { - use inner::SchemeInner; - bridge.lock().unwrap().active.store(true, Ordering::Release); - let mut scheme = match SchemeInner::new(scheme_name, Arc::clone(&bridge), mac, bssid) { - Ok(s) => s, - Err(e) => { - log::error!("bridge::scheme: failed to create scheme: {}", e); - std::process::exit(1); - } - }; - log::info!("bridge::scheme: event loop running on {}", scheme_name); - loop { - // Process incoming scheme requests - match scheme.tick() { - Ok(true) => { /* handled one request */ } - Ok(false) => { /* no work */ } - Err(e) => { - log::error!("bridge::scheme: tick error: {}", e); - // Continue — don't crash on transient errors - } + match inner::run(scheme_name, bridge, mac, bssid) { + Ok(()) => std::process::exit(0), + Err(e) => { + log::error!("bridge::scheme: event loop error: {}", e); + std::process::exit(1); } - - // Drain pending TX - let tx_frame = { - let mut b = bridge.lock().unwrap(); - b.take_tx() - }; - if let Some(eth) = tx_frame { - let (bssid_copy, mac_copy) = { - let b = bridge.lock().unwrap(); - (b.bssid, b.mac) - }; - if let Some(wifi) = ethernet_to_wifi(ð, &bssid_copy, &mac_copy) { - unsafe { - let rc = inner::rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()); - if rc != 0 { - bridge.lock().unwrap().stats.inc_tx_dropped(); - } else { - bridge.lock().unwrap().stats.inc_tx_frame(eth.len()); - } - } - } else { - bridge.lock().unwrap().stats.inc_tx_dropped(); - } - } - - // Notify readers if data is available - if bridge.lock().unwrap().available_for_read() > 0 { - scheme.notify_readers(); - } - - // Small yield to avoid busy-looping when idle - std::thread::sleep(std::time::Duration::from_millis(1)); } } diff --git a/local/recipes/system/redbear-authd/source/Cargo.lock b/local/recipes/system/redbear-authd/source/Cargo.lock index b3d2919336..1d091667fd 100644 --- a/local/recipes/system/redbear-authd/source/Cargo.lock +++ b/local/recipes/system/redbear-authd/source/Cargo.lock @@ -169,7 +169,7 @@ dependencies = [ [[package]] name = "redbear-authd" -version = "0.3.0" +version = "0.3.1" dependencies = [ "redbear-login-protocol", "rust-argon2", @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "redbear-login-protocol" -version = "0.3.0" +version = "0.3.1" dependencies = [ "serde", "serde_json",