1use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
19use core::num::NonZeroI32;
20
21use air_sys_types::{
22 AbsAxis, Errno, EventClock, EventType, InputAbsInfo, InputEvent, InputId, UEventGroups,
23 UEventMessage, UEventSocketFlags,
24};
25
26#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
27compile_error!("air-sys-syscall::device supporte uniquement x86_64 et aarch64 (ADR-014).");
28
29const AF_NETLINK: i32 = 16;
34const SOCK_RAW: i32 = 3;
35const SOCK_CLOEXEC: i32 = 0x8_0000;
36const NETLINK_KOBJECT_UEVENT: i32 = 15;
37const SOL_SOCKET: i32 = 1;
38const SO_PASSCRED: i32 = 16;
39const SCM_CREDENTIALS: i32 = 2;
40
41const EVIOC_TYPE: u8 = b'E';
43const IOC_WRITE: u32 = 1;
45const IOC_READ: u32 = 2;
46const IOC_SIZE_MASK: usize = 0x3fff;
48
49#[derive(Debug)]
57pub struct UEventSocket {
58 fd: OwnedFd,
59 check_credentials: bool,
62}
63
64impl UEventSocket {
65 #[must_use]
67 pub fn as_fd(&self) -> BorrowedFd<'_> {
68 use air_sys_types::fd::AsFd;
69 self.fd.as_fd()
70 }
71
72 #[must_use]
74 pub fn into_fd(self) -> OwnedFd {
75 self.fd
76 }
77
78 pub fn read<'b>(&self, buffer: &'b mut [u8]) -> Result<UEventMessage<'b>, Errno> {
95 let mut source = SockAddrNl::zeroed();
96 let mut cmsg_buf = [0_u8; CMSG_BUFFER_SIZE];
97
98 let mut iov = Iovec {
99 iov_base: buffer.as_mut_ptr(),
100 iov_len: buffer.len(),
101 };
102 let mut msg = MsgHdr {
103 msg_name: (&raw mut source).cast::<u8>(),
104 msg_namelen: SOCKADDR_NL_SIZE,
105 msg_iov: &raw mut iov,
106 msg_iovlen: 1,
107 msg_control: cmsg_buf.as_mut_ptr(),
108 msg_controllen: cmsg_buf.len(),
109 msg_flags: 0,
110 };
111
112 let ret = unsafe { raw_syscall_recvmsg(self.fd.as_raw_fd(), &raw mut msg, 0) };
117 if ret < 0 {
118 return Err(errno_from_negative_syscall_ret(ret));
119 }
120 let received = usize::try_from(ret).map_err(|_| Errno::EBADMSG)?;
121 let controllen = msg.msg_controllen.min(cmsg_buf.len());
122 let control = cmsg_buf.get(..controllen).unwrap_or(&[]);
123
124 authenticate_and_decode(
125 msg.msg_flags,
126 msg.msg_namelen,
127 source.nl_pid,
128 self.check_credentials,
129 control,
130 received,
131 buffer,
132 )
133 }
134}
135
136fn authenticate_and_decode<'b>(
141 msg_flags: i32,
142 namelen: u32,
143 nl_pid: u32,
144 check_credentials: bool,
145 control: &[u8],
146 received: usize,
147 buffer: &'b mut [u8],
148) -> Result<UEventMessage<'b>, Errno> {
149 if (msg_flags & MSG_TRUNC) != 0 {
150 return Err(Errno::EMSGSIZE);
151 }
152 if namelen < SOCKADDR_NL_SIZE || nl_pid != 0 {
154 return Err(Errno::EPERM);
155 }
156 if check_credentials && credential_uid(control) != Some(0) {
158 return Err(Errno::EPERM);
159 }
160 let received = received.min(buffer.len());
161 let payload = buffer.get(..received).unwrap_or(buffer);
162 UEventMessage::parse(payload)
163}
164
165pub fn uevent_socket_open(
175 groups: UEventGroups,
176 flags: UEventSocketFlags,
177) -> Result<UEventSocket, Errno> {
178 if groups.is_empty() {
179 return Err(Errno::EINVAL);
180 }
181
182 let socket_type = SOCK_RAW | SOCK_CLOEXEC | (flags.bits() & UEventSocketFlags::NONBLOCK.bits());
183 let ret = unsafe { raw_syscall_socket(AF_NETLINK, socket_type, NETLINK_KOBJECT_UEVENT) };
186 if ret < 0 {
187 return Err(errno_from_negative_syscall_ret(ret));
188 }
189 #[allow(clippy::cast_possible_truncation)]
190 let raw_fd = ret as i32;
191 let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
195
196 let check_credentials = groups.contains(UEventGroups::USERSPACE);
197 if check_credentials {
198 let enable: i32 = 1;
199 let ret = unsafe {
202 raw_syscall_setsockopt(
203 fd.as_raw_fd(),
204 SOL_SOCKET,
205 SO_PASSCRED,
206 (&raw const enable).cast::<u8>(),
207 4,
208 )
209 };
210 if ret < 0 {
211 return Err(errno_from_negative_syscall_ret(ret));
212 }
213 }
214
215 let addr = SockAddrNl {
216 nl_family: u16::try_from(AF_NETLINK).expect("AF_NETLINK tient en u16"),
217 nl_pad: 0,
218 nl_pid: 0, nl_groups: groups.bits(),
220 };
221 let ret = unsafe {
223 raw_syscall_bind(
224 fd.as_raw_fd(),
225 (&raw const addr).cast::<u8>(),
226 SOCKADDR_NL_SIZE,
227 )
228 };
229 if ret < 0 {
230 return Err(errno_from_negative_syscall_ret(ret));
231 }
232
233 Ok(UEventSocket {
234 fd,
235 check_credentials,
236 })
237}
238
239const SOCKADDR_NL_SIZE: u32 = 12;
244const MSG_TRUNC: i32 = 0x20;
245const CMSG_BUFFER_SIZE: usize = 64;
247
248#[repr(C)]
249#[derive(Clone, Copy)]
250struct SockAddrNl {
251 nl_family: u16,
252 nl_pad: u16,
253 nl_pid: u32,
254 nl_groups: u32,
255}
256
257impl SockAddrNl {
258 const fn zeroed() -> Self {
259 Self {
260 nl_family: 0,
261 nl_pad: 0,
262 nl_pid: 0,
263 nl_groups: 0,
264 }
265 }
266}
267
268#[repr(C)]
269struct Iovec {
270 iov_base: *mut u8,
271 iov_len: usize,
272}
273
274#[repr(C)]
275struct MsgHdr {
276 msg_name: *mut u8,
277 msg_namelen: u32,
278 msg_iov: *mut Iovec,
279 msg_iovlen: usize,
280 msg_control: *mut u8,
281 msg_controllen: usize,
282 msg_flags: i32,
283}
284
285fn credential_uid(control: &[u8]) -> Option<u32> {
290 const CMSG_HDR_SIZE: usize = 16;
292 const CMSG_DATA_OFFSET: usize = 16;
294 const UCRED_UID_OFFSET: usize = 4;
296
297 let mut offset = 0_usize;
298 loop {
299 let header_end = offset.checked_add(CMSG_HDR_SIZE)?;
300 let header = control.get(offset..header_end)?;
301 let len_bytes: [u8; 8] = header.get(0..8)?.try_into().ok()?;
302 let level_bytes: [u8; 4] = header.get(8..12)?.try_into().ok()?;
303 let type_bytes: [u8; 4] = header.get(12..16)?.try_into().ok()?;
304 let cmsg_len = usize::try_from(u64::from_ne_bytes(len_bytes)).ok()?;
305 let cmsg_level = i32::from_ne_bytes(level_bytes);
306 let cmsg_type = i32::from_ne_bytes(type_bytes);
307
308 if cmsg_len < CMSG_HDR_SIZE {
309 return None;
310 }
311 if cmsg_level == SOL_SOCKET && cmsg_type == SCM_CREDENTIALS {
312 let uid_start = offset
313 .checked_add(CMSG_DATA_OFFSET)?
314 .checked_add(UCRED_UID_OFFSET)?;
315 let uid_end = uid_start.checked_add(4)?;
316 let uid_bytes: [u8; 4] = control.get(uid_start..uid_end)?.try_into().ok()?;
317 return Some(u32::from_ne_bytes(uid_bytes));
318 }
319 let aligned = cmsg_len.checked_add(7)? & !7_usize;
321 offset = offset.checked_add(aligned)?;
322 }
323}
324
325pub fn evdev_read_events(
341 device: BorrowedFd<'_>,
342 events: &mut [InputEvent],
343) -> Result<usize, Errno> {
344 let size = core::mem::size_of::<InputEvent>();
345 let byte_len = events.len().checked_mul(size).ok_or(Errno::EINVAL)?;
346 let ptr = events.as_mut_ptr().cast::<u8>();
347 let ret = unsafe { raw_syscall_read(device.as_raw_fd(), ptr as u64, byte_len as u64) };
351 if ret < 0 {
352 return Err(errno_from_negative_syscall_ret(ret));
353 }
354 let bytes = usize::try_from(ret).unwrap_or(0);
355 let rem = bytes.checked_rem(size).unwrap_or(0);
356 if rem != 0 {
357 return Err(Errno::EPROTO);
358 }
359 Ok(bytes.checked_div(size).unwrap_or(0))
360}
361
362const fn ioc(dir: u32, ty: u8, nr: u8, size: u32) -> u32 {
368 (dir << 30) | (size << 16) | ((ty as u32) << 8) | (nr as u32)
369}
370
371fn evdev_ioctl_read_struct(
373 device: BorrowedFd<'_>,
374 nr: u8,
375 out: *mut u8,
376 size: u32,
377) -> Result<(), Errno> {
378 let request = ioc(IOC_READ, EVIOC_TYPE, nr, size);
379 let ret = unsafe { raw_syscall_ioctl(device.as_raw_fd(), u64::from(request), out as u64) };
383 if ret < 0 {
384 return Err(errno_from_negative_syscall_ret(ret));
385 }
386 Ok(())
387}
388
389fn evdev_ioctl_read_buffer<'b>(
394 device: BorrowedFd<'_>,
395 nr: u8,
396 buffer: &'b mut [u8],
397) -> Result<&'b [u8], Errno> {
398 let capped = buffer.len().min(IOC_SIZE_MASK);
399 let size = u32::try_from(capped).expect("capped ≤ 0x3fff tient en u32");
400 let request = ioc(IOC_READ, EVIOC_TYPE, nr, size);
401 let ptr = buffer.as_mut_ptr();
402 let ret = unsafe { raw_syscall_ioctl(device.as_raw_fd(), u64::from(request), ptr as u64) };
405 if ret < 0 {
406 return Err(errno_from_negative_syscall_ret(ret));
407 }
408 let written = usize::try_from(ret).unwrap_or(0).min(buffer.len());
409 Ok(buffer.get(..written).unwrap_or(buffer))
410}
411
412fn evdev_ioctl_write(device: BorrowedFd<'_>, nr: u8, size: u32, arg: u64) -> Result<(), Errno> {
414 let request = ioc(IOC_WRITE, EVIOC_TYPE, nr, size);
415 let ret = unsafe { raw_syscall_ioctl(device.as_raw_fd(), u64::from(request), arg) };
420 if ret < 0 {
421 return Err(errno_from_negative_syscall_ret(ret));
422 }
423 Ok(())
424}
425
426pub fn evdev_driver_version(device: BorrowedFd<'_>) -> Result<u32, Errno> {
436 let mut version: i32 = 0;
437 evdev_ioctl_read_struct(device, 0x01, (&raw mut version).cast::<u8>(), 4)?;
438 #[allow(clippy::cast_sign_loss)]
439 Ok(version as u32)
440}
441
442pub fn evdev_device_id(device: BorrowedFd<'_>) -> Result<InputId, Errno> {
448 let mut id = InputId::default();
449 let size = u32::try_from(core::mem::size_of::<InputId>()).expect("input_id tient en u32");
450 evdev_ioctl_read_struct(device, 0x02, (&raw mut id).cast::<u8>(), size)?;
451 Ok(id)
452}
453
454pub fn evdev_name<'b>(device: BorrowedFd<'_>, buffer: &'b mut [u8]) -> Result<&'b [u8], Errno> {
460 evdev_ioctl_read_buffer(device, 0x06, buffer)
461}
462
463pub fn evdev_physical_location<'b>(
469 device: BorrowedFd<'_>,
470 buffer: &'b mut [u8],
471) -> Result<&'b [u8], Errno> {
472 evdev_ioctl_read_buffer(device, 0x07, buffer)
473}
474
475pub fn evdev_unique_id<'b>(
481 device: BorrowedFd<'_>,
482 buffer: &'b mut [u8],
483) -> Result<&'b [u8], Errno> {
484 evdev_ioctl_read_buffer(device, 0x08, buffer)
485}
486
487pub fn evdev_supported_codes<'b>(
499 device: BorrowedFd<'_>,
500 event_type: Option<EventType>,
501 bitmap: &'b mut [u8],
502) -> Result<&'b [u8], Errno> {
503 let ev = match event_type {
504 Some(ty) => u8::try_from(ty.to_raw()).map_err(|_| Errno::EINVAL)?,
505 None => 0,
506 };
507 let nr = 0x20_u8.checked_add(ev).ok_or(Errno::EINVAL)?;
509 evdev_ioctl_read_buffer(device, nr, bitmap)
510}
511
512pub fn evdev_properties<'b>(
518 device: BorrowedFd<'_>,
519 bitmap: &'b mut [u8],
520) -> Result<&'b [u8], Errno> {
521 evdev_ioctl_read_buffer(device, 0x09, bitmap)
522}
523
524pub fn evdev_abs_info(device: BorrowedFd<'_>, axis: AbsAxis) -> Result<InputAbsInfo, Errno> {
531 let abs = u8::try_from(axis.to_raw()).map_err(|_| Errno::EINVAL)?;
532 let nr = 0x40_u8.checked_add(abs).ok_or(Errno::EINVAL)?;
534 let mut info = InputAbsInfo::default();
535 let size =
536 u32::try_from(core::mem::size_of::<InputAbsInfo>()).expect("input_absinfo tient en u32");
537 evdev_ioctl_read_struct(device, nr, (&raw mut info).cast::<u8>(), size)?;
538 Ok(info)
539}
540
541pub fn evdev_key_state<'b>(d: BorrowedFd<'_>, bitmap: &'b mut [u8]) -> Result<&'b [u8], Errno> {
551 evdev_ioctl_read_buffer(d, 0x18, bitmap)
552}
553
554pub fn evdev_led_state<'b>(d: BorrowedFd<'_>, bitmap: &'b mut [u8]) -> Result<&'b [u8], Errno> {
560 evdev_ioctl_read_buffer(d, 0x19, bitmap)
561}
562
563pub fn evdev_sound_state<'b>(d: BorrowedFd<'_>, bitmap: &'b mut [u8]) -> Result<&'b [u8], Errno> {
569 evdev_ioctl_read_buffer(d, 0x1a, bitmap)
570}
571
572pub fn evdev_switch_state<'b>(d: BorrowedFd<'_>, bitmap: &'b mut [u8]) -> Result<&'b [u8], Errno> {
578 evdev_ioctl_read_buffer(d, 0x1b, bitmap)
579}
580
581pub fn evdev_grab(device: BorrowedFd<'_>) -> Result<(), Errno> {
591 evdev_ioctl_write(device, 0x90, 4, 1)
592}
593
594pub fn evdev_release(device: BorrowedFd<'_>) -> Result<(), Errno> {
600 evdev_ioctl_write(device, 0x90, 4, 0)
601}
602
603pub fn evdev_revoke(device: BorrowedFd<'_>) -> Result<(), Errno> {
609 evdev_ioctl_write(device, 0x91, 4, 0)
610}
611
612pub fn evdev_set_clock(device: BorrowedFd<'_>, clock: EventClock) -> Result<(), Errno> {
618 let clockid: i32 = clock.to_clockid();
619 evdev_ioctl_write(device, 0xa0, 4, (&raw const clockid) as u64)
620}
621
622#[cfg(target_arch = "x86_64")]
627#[inline]
628unsafe fn raw_syscall_socket(domain: i32, ty: i32, protocol: i32) -> i64 {
629 let ret: i64;
630 unsafe {
632 core::arch::asm!(
633 "syscall",
634 in("rax") 41_i64,
635 in("rdi") i64::from(domain),
636 in("rsi") i64::from(ty),
637 in("rdx") i64::from(protocol),
638 lateout("rax") ret,
639 lateout("rcx") _,
640 lateout("r11") _,
641 options(nostack, preserves_flags, readonly),
642 );
643 }
644 ret
645}
646
647#[cfg(target_arch = "aarch64")]
648#[inline]
649unsafe fn raw_syscall_socket(domain: i32, ty: i32, protocol: i32) -> i64 {
650 let ret: i64;
651 unsafe {
653 core::arch::asm!(
654 "svc 0",
655 in("x8") 198_i64,
656 inout("x0") i64::from(domain) => ret,
657 in("x1") i64::from(ty),
658 in("x2") i64::from(protocol),
659 options(nostack, preserves_flags, readonly),
660 );
661 }
662 ret
663}
664
665#[cfg(target_arch = "x86_64")]
666#[inline]
667unsafe fn raw_syscall_bind(fd: i32, address: *const u8, addrlen: u32) -> i64 {
668 let ret: i64;
669 unsafe {
672 core::arch::asm!(
673 "syscall",
674 in("rax") 49_i64,
675 in("rdi") i64::from(fd),
676 in("rsi") address as u64,
677 in("rdx") u64::from(addrlen),
678 lateout("rax") ret,
679 lateout("rcx") _,
680 lateout("r11") _,
681 options(nostack, preserves_flags, readonly),
682 );
683 }
684 ret
685}
686
687#[cfg(target_arch = "aarch64")]
688#[inline]
689unsafe fn raw_syscall_bind(fd: i32, address: *const u8, addrlen: u32) -> i64 {
690 let ret: i64;
691 unsafe {
693 core::arch::asm!(
694 "svc 0",
695 in("x8") 200_i64,
696 inout("x0") i64::from(fd) => ret,
697 in("x1") address as u64,
698 in("x2") u64::from(addrlen),
699 options(nostack, preserves_flags, readonly),
700 );
701 }
702 ret
703}
704
705#[cfg(target_arch = "x86_64")]
706#[inline]
707unsafe fn raw_syscall_setsockopt(
708 fd: i32,
709 level: i32,
710 optname: i32,
711 optval: *const u8,
712 optlen: u32,
713) -> i64 {
714 let ret: i64;
715 unsafe {
718 core::arch::asm!(
719 "syscall",
720 in("rax") 54_i64,
721 in("rdi") i64::from(fd),
722 in("rsi") i64::from(level),
723 in("rdx") i64::from(optname),
724 in("r10") optval as u64,
725 in("r8") u64::from(optlen),
726 lateout("rax") ret,
727 lateout("rcx") _,
728 lateout("r11") _,
729 options(nostack, preserves_flags, readonly),
730 );
731 }
732 ret
733}
734
735#[cfg(target_arch = "aarch64")]
736#[inline]
737unsafe fn raw_syscall_setsockopt(
738 fd: i32,
739 level: i32,
740 optname: i32,
741 optval: *const u8,
742 optlen: u32,
743) -> i64 {
744 let ret: i64;
745 unsafe {
747 core::arch::asm!(
748 "svc 0",
749 in("x8") 208_i64,
750 inout("x0") i64::from(fd) => ret,
751 in("x1") i64::from(level),
752 in("x2") i64::from(optname),
753 in("x3") optval as u64,
754 in("x4") u64::from(optlen),
755 options(nostack, preserves_flags, readonly),
756 );
757 }
758 ret
759}
760
761#[cfg(target_arch = "x86_64")]
762#[inline]
763unsafe fn raw_syscall_recvmsg(fd: i32, msg: *mut MsgHdr, flags: i32) -> i64 {
764 let ret: i64;
765 unsafe {
768 core::arch::asm!(
769 "syscall",
770 in("rax") 47_i64,
771 in("rdi") i64::from(fd),
772 in("rsi") msg as u64,
773 in("rdx") i64::from(flags),
774 lateout("rax") ret,
775 lateout("rcx") _,
776 lateout("r11") _,
777 options(nostack, preserves_flags),
778 );
779 }
780 ret
781}
782
783#[cfg(target_arch = "aarch64")]
784#[inline]
785unsafe fn raw_syscall_recvmsg(fd: i32, msg: *mut MsgHdr, flags: i32) -> i64 {
786 let ret: i64;
787 unsafe {
789 core::arch::asm!(
790 "svc 0",
791 in("x8") 212_i64,
792 inout("x0") i64::from(fd) => ret,
793 in("x1") msg as u64,
794 in("x2") i64::from(flags),
795 options(nostack, preserves_flags),
796 );
797 }
798 ret
799}
800
801#[cfg(target_arch = "x86_64")]
802#[inline]
803unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
804 let ret: i64;
805 unsafe {
808 core::arch::asm!(
809 "syscall",
810 in("rax") 0_i64,
811 in("rdi") i64::from(fd),
812 in("rsi") buffer,
813 in("rdx") count,
814 lateout("rax") ret,
815 lateout("rcx") _,
816 lateout("r11") _,
817 options(nostack, preserves_flags),
818 );
819 }
820 ret
821}
822
823#[cfg(target_arch = "aarch64")]
824#[inline]
825unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
826 let ret: i64;
827 unsafe {
829 core::arch::asm!(
830 "svc 0",
831 in("x8") 63_i64,
832 inout("x0") i64::from(fd) => ret,
833 in("x1") buffer,
834 in("x2") count,
835 options(nostack, preserves_flags),
836 );
837 }
838 ret
839}
840
841#[cfg(target_arch = "x86_64")]
842#[inline]
843unsafe fn raw_syscall_ioctl(fd: i32, request: u64, arg: u64) -> i64 {
844 let ret: i64;
845 unsafe {
848 core::arch::asm!(
849 "syscall",
850 in("rax") 16_i64,
851 in("rdi") i64::from(fd),
852 in("rsi") request,
853 in("rdx") arg,
854 lateout("rax") ret,
855 lateout("rcx") _,
856 lateout("r11") _,
857 options(nostack, preserves_flags),
858 );
859 }
860 ret
861}
862
863#[cfg(target_arch = "aarch64")]
864#[inline]
865unsafe fn raw_syscall_ioctl(fd: i32, request: u64, arg: u64) -> i64 {
866 let ret: i64;
867 unsafe {
869 core::arch::asm!(
870 "svc 0",
871 in("x8") 29_i64,
872 inout("x0") i64::from(fd) => ret,
873 in("x1") request,
874 in("x2") arg,
875 options(nostack, preserves_flags),
876 );
877 }
878 ret
879}
880
881fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
886 debug_assert!(ret < 0 && ret > -4096);
887 #[allow(clippy::cast_possible_truncation)]
888 let raw = ret.wrapping_neg() as i32;
889 let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
890 Errno::from_nonzero(nz)
891}
892
893#[cfg(test)]
894mod tests;