Skip to main content

coreshift_core/
netlink.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Netlink socket helpers.
6//!
7//! Provides a non-blocking `NETLINK_KOBJECT_UEVENT` socket for receiving
8//! kernel uevents (power supply changes, hotplug events, etc.).
9
10use crate::CoreError;
11use crate::reactor::Fd;
12
13#[cfg(target_os = "android")]
14mod imp {
15    use super::*;
16    use std::os::unix::io::AsRawFd;
17
18    fn errno() -> i32 {
19        std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
20    }
21
22    pub fn uevent_open() -> Result<Fd, CoreError> {
23        unsafe {
24            let fd = libc::socket(
25                libc::AF_NETLINK,
26                libc::SOCK_RAW | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK,
27                libc::NETLINK_KOBJECT_UEVENT,
28            );
29            if fd < 0 {
30                return Err(CoreError::sys(errno(), "netlink socket"));
31            }
32            let mut addr: libc::sockaddr_nl = std::mem::zeroed();
33            addr.nl_family = libc::AF_NETLINK as u16;
34            addr.nl_pid    = 0;
35            addr.nl_groups = 1; // UGRP_KERNEL — all uevents
36            let r = libc::bind(
37                fd,
38                &addr as *const _ as *const libc::sockaddr,
39                std::mem::size_of::<libc::sockaddr_nl>() as libc::socklen_t,
40            );
41            if r < 0 {
42                libc::close(fd);
43                return Err(CoreError::sys(errno(), "netlink bind"));
44            }
45            Fd::from_owned_raw_fd(fd, "netlink_uevent")
46        }
47    }
48
49    pub fn uevent_recv_raw(fd: &Fd, buf: &mut [u8]) -> Option<usize> {
50        let n = unsafe {
51            libc::recv(
52                fd.as_raw_fd(),
53                buf.as_mut_ptr() as *mut libc::c_void,
54                buf.len(),
55                libc::MSG_DONTWAIT,
56            )
57        };
58        if n > 0 { Some(n as usize) } else { None }
59    }
60}
61
62#[cfg(not(target_os = "android"))]
63mod imp {
64    use super::*;
65
66    pub fn uevent_open() -> Result<Fd, CoreError> {
67        Err(CoreError::sys(libc::ENOSYS, "netlink uevent: android only"))
68    }
69
70    pub fn uevent_recv_raw(_fd: &Fd, _buf: &mut [u8]) -> Option<usize> {
71        None
72    }
73}
74
75/// Open a non-blocking `NETLINK_KOBJECT_UEVENT` socket bound to all kernel groups.
76///
77/// ### Errors
78/// - `EMFILE` / `ENFILE`: File descriptor limit hit.
79/// - `ENOMEM`: Insufficient kernel memory.
80/// - `EPERM`: Insufficient privilege to bind to the multicast group.
81pub fn uevent_open() -> Result<Fd, CoreError> {
82    imp::uevent_open()
83}
84
85/// Drain all pending uevent messages; return the last `power_supply` event seen.
86///
87/// Returns `(capacity, status)` where `capacity` is `None` if `POWER_SUPPLY_CAPACITY`
88/// was absent from the uevent (caller should fall back to sysfs).
89/// Returns `None` if the queue contained no `power_supply` events at all.
90/// Must be called in a loop after `EPOLLET` fires to drain the edge-triggered fd.
91pub fn uevent_drain_battery(fd: &Fd) -> Option<(Option<u8>, String)> {
92    let mut result: Option<(Option<u8>, String)> = None;
93    let mut buf = [0u8; 4096];
94    loop {
95        match imp::uevent_recv_raw(fd, &mut buf) {
96            None => break,
97            Some(n) => {
98                if let Some(batt) = parse_battery(&buf[..n]) {
99                    result = Some(batt);
100                }
101            }
102        }
103    }
104    result
105}
106
107/// Receive one raw uevent message. Returns the number of bytes written into `buf`,
108/// or `None` if no message is pending (`EAGAIN`).
109pub fn uevent_recv(fd: &Fd, buf: &mut [u8]) -> Option<usize> {
110    imp::uevent_recv_raw(fd, buf)
111}
112
113fn parse_battery(msg: &[u8]) -> Option<(Option<u8>, String)> {
114    let parts: Vec<&str> = msg
115        .split(|&b| b == 0)
116        .filter_map(|s| std::str::from_utf8(s).ok())
117        .filter(|s| !s.is_empty())
118        .collect();
119
120    if !parts.iter().any(|s| *s == "SUBSYSTEM=power_supply") {
121        return None;
122    }
123
124    let cap = parts.iter()
125        .find_map(|s| s.strip_prefix("POWER_SUPPLY_CAPACITY="))
126        .and_then(|v| v.parse::<u8>().ok());
127
128    let status = parts.iter()
129        .find_map(|s| s.strip_prefix("POWER_SUPPLY_STATUS="))
130        .unwrap_or("Unknown")
131        .to_string();
132
133    Some((cap, status))
134}