#[expect(
non_camel_case_types,
dead_code,
reason = "bindgen output: C type names, and UAPI items not used yet (module doc)"
)]
mod raw {
include!("sys.rs");
}
pub use raw::*;
pub mod binder {
pub use crate::sys::*;
pub use crate::sys::__kernel_pid_t as pid_t;
pub use crate::sys::__kernel_uid32_t as uid_t;
use rustix::{io, ioctl};
use std::os::fd::AsFd;
pub(crate) unsafe fn write_read<Fd: AsFd>(
fd: Fd,
write: &[u8],
read: &mut [u8],
progress: &mut binder_write_read,
) -> std::result::Result<(), io::Errno> {
let words = read.len() & !3;
let read = &mut read[..words];
if progress.write_consumed > write.len() as binder_size_t
|| progress.read_consumed > read.len() as binder_size_t
|| progress.read_consumed % 4 != 0
{
return Err(io::Errno::INVAL);
}
progress.write_size = write.len() as binder_size_t;
progress.write_buffer = write.as_ptr() as binder_uintptr_t;
progress.read_size = read.len() as binder_size_t;
progress.read_buffer = read.as_mut_ptr() as binder_uintptr_t;
unsafe {
let ctl = ioctl::Updater::<
{ ioctl::opcode::read_write::<binder_write_read>(b'b', 1) },
_,
>::new(progress);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn set_max_threads<Fd: AsFd>(
fd: Fd,
max_threads: u32,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl =
ioctl::Setter::<{ ioctl::opcode::write::<__u32>(b'b', 5) }, _>::new(max_threads);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn set_context_mgr<Fd: AsFd>(
fd: Fd,
pid: i32,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Setter::<{ ioctl::opcode::write::<__s32>(b'b', 7) }, _>::new(pid);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn version<Fd: AsFd>(
fd: Fd,
ver: &mut binder_version,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl =
ioctl::Updater::<{ ioctl::opcode::read_write::<binder_version>(b'b', 9) }, _>::new(
ver,
);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn set_context_mgr_ext<Fd: AsFd>(
fd: Fd,
obj: flat_binder_object,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl =
ioctl::Setter::<{ ioctl::opcode::write::<flat_binder_object>(b'b', 13) }, _>::new(
obj,
);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn enable_oneway_spam_detection<Fd: AsFd>(
fd: Fd,
enable: __u32,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Setter::<{ ioctl::opcode::write::<__u32>(b'b', 16) }, _>::new(enable);
ioctl::ioctl(fd, ctl)
}
}
#[cfg(not(test))]
pub(crate) fn binder_ctl_add<Fd: AsFd>(
fd: Fd,
device: &mut binderfs_device,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl =
ioctl::Updater::<{ ioctl::opcode::read_write::<binderfs_device>(b'b', 1) }, _>::new(
device,
);
ioctl::ioctl(fd, ctl)
}
}
#[expect(
dead_code,
reason = "BINDER_SET_IDLE_TIMEOUT is a no-op in every shipping driver; kept for the UAPI"
)]
pub(crate) fn set_idle_timeout<Fd: AsFd>(
fd: Fd,
timeout: i64,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Setter::<{ ioctl::opcode::write::<i64>(b'b', 3) }, _>::new(timeout);
ioctl::ioctl(fd, ctl)
}
}
#[expect(
dead_code,
reason = "BINDER_SET_IDLE_PRIORITY is a no-op in every shipping driver; kept for the UAPI"
)]
pub(crate) fn set_idle_priority<Fd: AsFd>(
fd: Fd,
priority: i32,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Setter::<{ ioctl::opcode::write::<__s32>(b'b', 6) }, _>::new(priority);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn thread_exit<Fd: AsFd>(fd: Fd, pid: i32) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Setter::<{ ioctl::opcode::write::<__s32>(b'b', 8) }, _>::new(pid);
ioctl::ioctl(fd, ctl)
}
}
#[expect(dead_code, reason = "debug-only ioctl; no crate path issues it yet")]
pub(crate) fn get_node_debug_info<Fd: AsFd>(
fd: Fd,
node_debug_info: &mut binder_node_debug_info,
) -> std::result::Result<(), rustix::io::Errno> {
unsafe {
let ctl = ioctl::Updater::<
{ ioctl::opcode::read_write::<binder_node_debug_info>(b'b', 11) },
_,
>::new(node_debug_info);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn get_node_info_for_ref<Fd: AsFd>(
fd: Fd,
node_info: &mut binder_node_info_for_ref,
) -> std::result::Result<(), rustix::io::Errno> {
unsafe {
let ctl = ioctl::Updater::<
{ ioctl::opcode::read_write::<binder_node_info_for_ref>(b'b', 12) },
_,
>::new(node_info);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn freeze<Fd: AsFd>(
fd: Fd,
info: binder_freeze_info,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl =
ioctl::Setter::<{ ioctl::opcode::write::<binder_freeze_info>(b'b', 14) }, _>::new(
info,
);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn get_frozen_info<Fd: AsFd>(
fd: Fd,
frozen_info: &mut binder_frozen_status_info,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Updater::<
{ ioctl::opcode::read_write::<binder_frozen_status_info>(b'b', 15) },
_,
>::new(frozen_info);
ioctl::ioctl(fd, ctl)
}
}
pub(crate) fn get_extended_error<Fd: AsFd>(
fd: Fd,
ee: &mut binder_extended_error,
) -> std::result::Result<(), io::Errno> {
unsafe {
let ctl = ioctl::Updater::<
{ ioctl::opcode::read_write::<binder_extended_error>(b'b', 17) },
_,
>::new(ee);
ioctl::ioctl(fd, ctl)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn progress(write_consumed: u64, read_consumed: u64) -> binder_write_read {
binder_write_read {
write_size: 0,
write_consumed,
write_buffer: 0,
read_size: 0,
read_consumed,
read_buffer: 0,
}
}
fn call(write: &[u8], read: &mut [u8], bwr: &mut binder_write_read) -> io::Errno {
let null = std::fs::File::open("/dev/null").expect("/dev/null");
unsafe { write_read(&null, write, read, bwr) }.expect_err("not a binder fd")
}
#[test]
fn write_read_refuses_a_consumed_count_outside_its_buffer() {
let mut read = [0u8; 8];
assert_eq!(
call(&[0; 4], &mut read, &mut progress(5, 0)),
io::Errno::INVAL
);
assert_eq!(call(&[], &mut read, &mut progress(0, 12)), io::Errno::INVAL);
assert_eq!(call(&[], &mut read, &mut progress(0, 2)), io::Errno::INVAL);
assert_ne!(
call(&[0; 4], &mut read, &mut progress(4, 8)),
io::Errno::INVAL
);
}
#[test]
fn write_read_lends_the_read_buffer_in_whole_words() {
let mut read = [0u8; 7];
let mut bwr = progress(0, 0);
call(&[0; 4], &mut read, &mut bwr);
assert_eq!((bwr.write_size, bwr.read_size), (4, 4));
call(&[], &mut read[..3], &mut bwr);
assert_eq!(bwr.read_size, 0);
}
}
}