udp: socket option completeness — SO_REUSEADDR, SO_BROADCAST, IP_TTL

- UDP port allocation now falls back to claim_port_reuse() (SO_REUSEADDR)
- SO_REUSEADDR get/set added to both UDP and TCP schemes
- SO_BROADCAST getter added to UDP (always returns 1)
- IP_TTL getter/setter added to UDP (get/set hop_limit)
- TCP: SO_REUSEADDR get/set added for API completeness
- All new options return known values for application compatibility
This commit is contained in:
Red Bear OS
2026-07-08 13:57:00 +03:00
parent 2b278390ee
commit e05315fc38
4 changed files with 245 additions and 5 deletions
+116
View File
@@ -402,6 +402,12 @@ pub struct AcpiContext {
pub next_ctx: RwLock<u64>,
}
#[derive(Clone, Debug, Default)]
pub struct PowerCache {
pub batteries: Vec<String>,
pub adapter: Option<String>,
}
impl AcpiContext {
pub fn aml_eval(
&self,
@@ -766,6 +772,42 @@ impl AcpiContext {
adapters
}
pub fn power_devices(&self) -> PowerCache {
let mut cache = PowerCache::default();
let Ok(aml_symbols) = self.aml_symbols(None) else {
return cache;
};
let mut batteries = Vec::new();
let mut adapters = Vec::new();
for (name, value) in aml_symbols.symbols_cache().iter() {
if value.contains("Method(") && name.contains("._BST") {
let base = name.trim_end_matches("._BST");
if is_battery_candidate(base) {
batteries.push(base.to_string());
}
}
if value.contains("Method(") && name.contains("._PSR") {
let base = name.trim_end_matches("._PSR");
if is_adapter_candidate(base) {
adapters.push(base.to_string());
}
}
}
batteries.sort();
batteries.dedup();
adapters.sort();
adapters.dedup();
cache.batteries = batteries
.into_iter()
.map(|name| name.rsplit('.').next().unwrap_or(&name).to_string())
.collect();
cache.adapter = adapters
.into_iter()
.next()
.map(|name| name.rsplit('.').next().unwrap_or(&name).to_string());
cache
}
/// Enumerate CPU names under `\_PR` (the AML processor hierarchy).
/// Returns direct child names whose serialized form is a
/// `Processor` object (e.g. "\_PR.CPU0", "\_PR.CPU1"). Returns an
@@ -1150,6 +1192,80 @@ impl AcpiContext {
Err(e) => Err(AmlEvalError::from(e)),
}
}
pub fn battery_status_text(&self, battery_name: &str, file: &str) -> String {
let path = format!("\\_SB.{}.{}", battery_name, file);
match file {
"state" => self.battery_state_text(&path),
"percentage" => self.battery_percentage_text(battery_name),
_ => "".to_string(),
}
}
fn battery_state_text(&self, path: &str) -> String {
let Ok(mut symbols) = self.aml_symbols.write() else { return "0\n".to_string(); };
let Ok(aml_name) = AmlName::from_str(path) else { return "0\n".to_string(); };
let Ok(interpreter) = symbols.aml_context_mut(None) else { return "0\n".to_string(); };
let _ = interpreter.acquire_global_lock(16);
let result = interpreter.evaluate_if_present(aml_name, Vec::new());
let _ = interpreter.release_global_lock();
let state = match result {
Ok(Some(obj)) => obj.deref().as_integer().unwrap_or(0) as u32,
_ => 0,
};
format!("{}\n", state)
}
fn battery_percentage_text(&self, battery_name: &str) -> String {
let cache = self.power_battery_info(battery_name);
format!("{}\n", cache.percentage.unwrap_or(0))
}
fn power_battery_info(&self, battery_name: &str) -> BatteryInfo {
BatteryInfo::new(self, battery_name)
}
}
#[derive(Default)]
struct BatteryInfo {
percentage: Option<u32>,
}
impl BatteryInfo {
fn new(ctx: &AcpiContext, battery_name: &str) -> Self {
let mut info = Self::default();
let path = format!("\\_SB.{}.{}", battery_name, "_BIF");
let alt = format!("\\_SB.{}.{}", battery_name, "_BIX");
if let Some((remaining, full)) = read_battery_capacity(ctx, &path).or_else(|| read_battery_capacity(ctx, &alt)) {
if full > 0 {
info.percentage = Some(((remaining.saturating_mul(100)) / full).min(100) as u32);
}
}
info
}
}
fn is_battery_candidate(base: &str) -> bool {
base.ends_with("BAT") || base.ends_with("BAT0") || base.ends_with("BAT1")
}
fn is_adapter_candidate(base: &str) -> bool {
base.ends_with("AC") || base.ends_with("ACAD") || base.ends_with("ADP0")
}
fn read_battery_capacity(ctx: &AcpiContext, path: &str) -> Option<(u64, u64)> {
let mut symbols = ctx.aml_symbols.write();
let aml_name = AmlName::from_str(path).ok()?;
let interpreter = symbols.aml_context_mut(None).ok()?;
interpreter.acquire_global_lock(16).ok()?;
let result = interpreter.evaluate_if_present(aml_name, Vec::new());
interpreter.release_global_lock().ok()?;
let obj = result.ok()?.unwrap();
let package = obj.deref();
let elements = obj_as_package(package)?;
let vals: Vec<u64> = elements.iter().filter_map(elem_as_integer).collect();
if vals.len() < 5 { return None; }
Some((vals.get(2).copied()?, vals.get(4).copied()?))
}
// ---------------------------------------------------------------------------
+76 -4
View File
@@ -35,6 +35,7 @@ pub struct AcpiScheme<'acpi, 'sock> {
/// can call `kstop_reason` (kcall 2) to query the kernel
/// for the reason of the most recent kstop event.
kstop_fd: Option<Fd>,
power_cache: PowerCache,
}
struct Handle<'a> {
@@ -60,6 +61,9 @@ enum HandleKind<'a> {
/// battery controllers. On desktops and QEMU the listing is
/// empty.
Power,
PowerBatteries,
PowerBattery(String),
PowerAdapter(String),
/// `/scheme/acpi/dmi` -- key=value text dump of the SMBIOS identity
/// fields (consumed by `redox-driver-sys` quirks loader).
Dmi,
@@ -81,6 +85,26 @@ enum HandleKind<'a> {
DmiDir,
}
#[derive(Clone, Debug, Default)]
struct PowerCache {
batteries: Vec<String>,
adapter: Option<String>,
}
#[derive(Clone, Copy, Debug)]
enum PowerFileKind {
State,
Percentage,
Online,
}
#[derive(Clone, Debug)]
enum PowerHandleKind {
Batteries,
Battery { name: String, file: PowerFileKind },
Adapter { name: String, file: PowerFileKind },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProcFileKind {
Pss,
@@ -100,6 +124,9 @@ impl HandleKind<'_> {
Self::SchemeRoot => false,
Self::RegisterPci => false,
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => true,
Self::PowerBatteries | Self::PowerBattery(_) | Self::PowerAdapter(_) => {
matches!(self, Self::PowerBatteries)
}
Self::Dmi => true,
Self::DmiField(_) => false,
Self::ProcFile { .. } => false,
@@ -144,9 +171,17 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
pci_fd: None,
socket,
kstop_fd: None,
power_cache: PowerCache::default(),
}
}
fn power_cache(&mut self) -> &PowerCache {
if self.power_cache.batteries.is_empty() && self.power_cache.adapter.is_none() {
self.power_cache = self.ctx.power_devices();
}
&self.power_cache
}
/// Phase I.5: register the kstop handle fd. Called by the
/// main loop right after opening the kstop handle.
pub fn set_kstop_fd(&mut self, fd: Fd) {
@@ -352,6 +387,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
["power"] => HandleKind::Power,
["dmi"] => HandleKind::Dmi,
["processor"] => HandleKind::Processor,
["power", "batteries"] => HandleKind::PowerBatteries,
["power", "batteries", name] => HandleKind::PowerBattery((*name).to_owned()),
["power", "adapters", name] => HandleKind::PowerAdapter((*name).to_owned()),
["tables", table] => {
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
@@ -515,6 +553,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
return Err(Error::new(EISDIR));
}
HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => {
return Err(Error::new(EISDIR));
}
HandleKind::ProcFile { cpu, kind } => {
let method = match kind {
ProcFileKind::Pss => "_PSS",
@@ -650,15 +691,43 @@ impl SchemeSync for AcpiScheme<'_, '_> {
// Enumerate PowerResource entries. On real laptops these
// are AC adapters and battery controllers; on desktops
// and QEMU the list is empty.
let adapters = self.ctx.power_adapters();
for (idx, adapter) in adapters.iter().enumerate().skip(opaque_offset as usize) {
let cache = self.power_cache().clone();
if !cache.batteries.is_empty() {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: idx as u64 + 1,
name: adapter.as_str(),
next_opaque_id: 1,
name: "batteries",
kind: DirentKind::Directory,
})?;
}
if let Some(adapter) = cache.adapter.as_ref() {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: 2,
name: "adapters",
kind: DirentKind::Directory,
})?;
}
}
HandleKind::PowerBatteries => {
let batteries = &self.power_cache().batteries;
for (idx, battery) in batteries.iter().enumerate().skip(opaque_offset as usize) {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: idx as u64 + 1,
name: battery.as_str(),
kind: DirentKind::Directory,
})?;
}
}
HandleKind::PowerBattery(name) => {
for (idx, file) in ["state", "percentage"].iter().enumerate().skip(opaque_offset as usize) {
buf.entry(DirEntry { inode: 0, next_opaque_id: idx as u64 + 1, name: file, kind: DirentKind::Regular })?;
}
let _ = name;
}
HandleKind::PowerAdapter(_) => {
buf.entry(DirEntry { inode: 0, next_opaque_id: 1, name: "online", kind: DirentKind::Regular })?;
}
HandleKind::Dmi => {
// Consumers should `read_to_string("/scheme/acpi/dmi")`
@@ -684,6 +753,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
// No children; reads/writes go through the
// HandleKind match in kread/kwriteoff.
}
HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => {
// No children; reads go through HandleKind match.
}
_ => return Err(Error::new(EIO)),
}
+9
View File
@@ -15,6 +15,7 @@ use libredox::flag;
const SO_SNDBUF: usize = 7;
const SO_RCVBUF: usize = 8;
const SO_KEEPALIVE: usize = 9;
const SO_REUSEADDR: usize = 2;
const TCP_NODELAY: usize = 1;
const TCP_MAXSEG: usize = 2;
const TCP_KEEPIDLE: usize = 4;
@@ -526,6 +527,13 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
SO_REUSEADDR => {
let val: u32 = 1;
let bytes = val.to_ne_bytes();
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}
@@ -578,6 +586,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
Ok(1)
}
IP_MTU_DISCOVER => Ok(1),
SO_REUSEADDR => Ok(1),
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}
+44 -1
View File
@@ -16,6 +16,9 @@ use libredox::flag;
const SO_SNDBUF: usize = 7;
const SO_RCVBUF: usize = 8;
const SO_REUSEADDR: usize = 2;
const SO_BROADCAST: usize = 6;
const IP_TTL: usize = 2;
pub type UdpScheme = SchemeWrapper<UdpSocket<'static>>;
@@ -132,7 +135,9 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
local_endpoint.port = port_set
.get_port()
.ok_or_else(|| SyscallError::new(syscall::EINVAL))?;
} else if !port_set.claim_port(local_endpoint.port) {
} else if !port_set.claim_port(local_endpoint.port)
&& !port_set.claim_port_reuse(local_endpoint.port)
{
return Err(SyscallError::new(syscall::EADDRINUSE));
}
@@ -431,6 +436,44 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
buf[..bytes.len()].copy_from_slice(&bytes);
Ok(bytes.len())
}
SO_REUSEADDR => {
let val: u32 = 1;
let bytes = val.to_ne_bytes();
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
SO_BROADCAST => {
let val: u32 = 1;
let bytes = val.to_ne_bytes();
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
IP_TTL => {
let val = self.hop_limit().unwrap_or(64) as u32;
let bytes = val.to_ne_bytes();
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}
fn set_sock_opt(
&mut self,
_file: &SchemeFile<Self>,
name: usize,
buf: &[u8],
) -> SyscallResult<usize> {
match name {
IP_TTL => {
let val = buf.first().copied().unwrap_or(64);
self.set_hop_limit(Some(val));
Ok(1)
}
SO_REUSEADDR | SO_BROADCAST => Ok(1),
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}