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 battery `(level_pct, status)` seen.
86///
87/// Parses messages for `SUBSYSTEM=power_supply` with `POWER_SUPPLY_CAPACITY`.
88/// Returns `None` if the queue contained no battery capacity events.
89/// Must be called in a loop after `EPOLLET` fires to drain the edge-triggered fd.
90pub fn uevent_drain_battery(fd: &Fd) -> Option<(u8, String)> {
91    let mut result: Option<(u8, String)> = None;
92    let mut buf = [0u8; 4096];
93    loop {
94        match imp::uevent_recv_raw(fd, &mut buf) {
95            None => break,
96            Some(n) => {
97                if let Some(batt) = parse_battery(&buf[..n]) {
98                    result = Some(batt);
99                }
100            }
101        }
102    }
103    result
104}
105
106fn parse_battery(msg: &[u8]) -> Option<(u8, String)> {
107    let parts: Vec<&str> = msg
108        .split(|&b| b == 0)
109        .filter_map(|s| std::str::from_utf8(s).ok())
110        .filter(|s| !s.is_empty())
111        .collect();
112
113    if !parts.iter().any(|s| *s == "SUBSYSTEM=power_supply") {
114        return None;
115    }
116
117    let cap = parts.iter()
118        .find_map(|s| s.strip_prefix("POWER_SUPPLY_CAPACITY="))
119        .and_then(|v| v.parse::<u8>().ok())?;
120
121    let status = parts.iter()
122        .find_map(|s| s.strip_prefix("POWER_SUPPLY_STATUS="))
123        .unwrap_or("Unknown")
124        .to_string();
125
126    Some((cap, status))
127}