use anyhow::Result; use rustix::net::{ bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix, SocketType, }; use rustix::path::Arg; use rustix::thread::gettid; use std::ffi::{c_char, c_void, CStr, CString}; use std::os::fd::{AsFd, AsRawFd}; use std::os::unix::net::{UnixListener}; use std::process::Command; use std::sync::OnceLock; use std::{ fs, io::{Read, Write}, os::unix::net::UnixStream, }; #[cfg(target_pointer_width = "64")] #[macro_export] macro_rules! lp_select { ($lp32:expr, $lp64:expr) => { $lp64 }; } #[cfg(target_pointer_width = "32")] #[macro_export] macro_rules! lp_select { ($lp32:expr, $lp64:expr) => { $lp32 }; } #[cfg(debug_assertions)] #[macro_export] macro_rules! debug_select { ($debug:expr, $release:expr) => { $debug }; } #[cfg(not(debug_assertions))] #[macro_export] macro_rules! debug_select { ($debug:expr, $release:expr) => { $release }; } pub struct LateInit { cell: OnceLock, } impl LateInit { pub const fn new() -> Self { LateInit { cell: OnceLock::new(), } } pub fn init(&self, value: T) { assert!(self.cell.set(value).is_ok()) } pub fn initiated(&self) -> bool { self.cell.get().is_some() } } impl std::ops::Deref for LateInit { type Target = T; fn deref(&self) -> &T { self.cell.get().unwrap() } } pub fn set_socket_create_context(context: &str) -> Result<()> { let path = "/proc/thread-self/attr/sockcreate"; match fs::write(path, context) { Ok(_) => Ok(()), Err(_) => { let path = format!( "/proc/self/task/{}/attr/sockcreate", gettid().as_raw_nonzero() ); fs::write(path, context)?; Ok(()) } } } pub fn get_current_attr() -> Result { let s = fs::read("/proc/self/attr/current")?; Ok(s.to_string_lossy().to_string()) } pub fn chcon(path: &str, context: &str) -> Result<()> { Command::new("chcon").arg(context).arg(path).status()?; Ok(()) } pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> { let tag = CString::new(tag)?; let message = CString::new(message)?; unsafe { __android_log_print(level, tag.as_ptr(), message.as_ptr()); } Ok(()) } pub fn get_property(name: &str) -> Result { let name = CString::new(name)?; let mut buf = vec![0u8; 92]; let prop = unsafe { __system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char); CStr::from_bytes_until_nul(&buf)? }; Ok(prop.to_string_lossy().to_string()) } #[allow(dead_code)] pub fn set_property(name: &str, value: &str) -> Result<()> { let name = CString::new(name)?; let value = CString::new(value)?; unsafe { __system_property_set(name.as_ptr(), value.as_ptr()); } Ok(()) } #[allow(dead_code)] pub fn wait_property(name: &str, serial: u32) -> Result { let name = CString::new(name)?; let info = unsafe { __system_property_find(name.as_ptr()) }; let mut serial = serial; unsafe { __system_property_wait(info, serial, &mut serial, std::ptr::null()); } Ok(serial) } #[allow(dead_code)] pub fn get_property_serial(name: &str) -> Result { let name = CString::new(name)?; let info = unsafe { __system_property_find(name.as_ptr()) }; Ok(unsafe { __system_property_serial(info) }) } pub fn switch_mount_namespace(pid: i32) -> Result<()> { let cwd = std::env::current_dir()?; let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?; rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?; std::env::set_current_dir(cwd)?; Ok(()) } pub trait UnixStreamExt { fn read_u8(&mut self) -> Result; fn read_u32(&mut self) -> Result; fn read_usize(&mut self) -> Result; fn read_string(&mut self) -> Result; fn write_u8(&mut self, value: u8) -> Result<()>; fn write_u32(&mut self, value: u32) -> Result<()>; fn write_usize(&mut self, value: usize) -> Result<()>; fn write_string(&mut self, value: &str) -> Result<()>; } impl UnixStreamExt for UnixStream { fn read_u8(&mut self) -> Result { let mut buf = [0u8; 1]; self.read_exact(&mut buf)?; Ok(buf[0]) } fn read_u32(&mut self) -> Result { let mut buf = [0u8; 4]; self.read_exact(&mut buf)?; Ok(u32::from_ne_bytes(buf)) } fn read_usize(&mut self) -> Result { let mut buf = [0u8; std::mem::size_of::()]; self.read_exact(&mut buf)?; Ok(usize::from_ne_bytes(buf)) } fn read_string(&mut self) -> Result { let len = self.read_usize()?; let mut buf = vec![0u8; len]; self.read_exact(&mut buf)?; Ok(String::from_utf8(buf)?) } fn write_u8(&mut self, value: u8) -> Result<()> { self.write_all(&value.to_ne_bytes())?; Ok(()) } fn write_u32(&mut self, value: u32) -> Result<()> { self.write_all(&value.to_ne_bytes())?; Ok(()) } fn write_usize(&mut self, value: usize) -> Result<()> { self.write_all(&value.to_ne_bytes())?; Ok(()) } fn write_string(&mut self, value: &str) -> Result<()> { self.write_usize(value.len())?; self.write_all(value.as_bytes())?; Ok(()) } } pub fn unix_listener_from_path(path: &str) -> Result { let _ = fs::remove_file(path); let addr = SocketAddrUnix::new(path)?; let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?; bind_unix(&socket, &addr)?; listen(&socket, 2)?; chcon(path, "u:object_r:magisk_file:s0")?; Ok(UnixListener::from(socket)) } pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> { // FIXME: shall we set create context every time? set_socket_create_context(get_current_attr()?.as_str())?; let addr = SocketAddrUnix::new(path.as_bytes())?; let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?; connect_unix(&socket, &addr)?; sendto_unix(socket, buf, SendFlags::empty(), &addr)?; set_socket_create_context("u:r:zygote:s0")?; Ok(()) } pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool { unsafe { let mut pfd = libc::pollfd { fd: stream.as_raw_fd(), events: libc::POLLIN, revents: 0, }; let timeout = if block { -1 } else { 0 }; libc::poll(&mut pfd, 1, timeout); if pfd.revents & !libc::POLLIN != 0 { return false; } } return true; } extern "C" { fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32; fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32; fn __system_property_set(name: *const c_char, value: *const c_char) -> u32; fn __system_property_find(name: *const c_char) -> *const c_void; fn __system_property_wait( info: *const c_void, old_serial: u32, new_serial: *mut u32, timeout: *const libc::timespec, ) -> bool; fn __system_property_serial(info: *const c_void) -> u32; }