Skip to main content

linux_api/
futex.rs

1use core::sync::atomic::AtomicU32;
2
3use linux_syscall::Result64 as LinuxSyscallResult64;
4use linux_syscall::syscall;
5
6use crate::errno::Errno;
7use crate::time::kernel_timespec;
8use crate::{bindings, const_conversions};
9
10pub use bindings::linux_robust_list_head;
11#[allow(non_camel_case_types)]
12pub type robust_list_head = linux_robust_list_head;
13unsafe impl shadow_pod::Pod for robust_list_head {}
14
15pub const FUTEX_CMD_MASK: i32 = bindings::LINUX_FUTEX_CMD_MASK;
16pub const FUTEX_BITSET_MATCH_ANY: u32 = bindings::LINUX_FUTEX_BITSET_MATCH_ANY;
17
18/// Represents a `FutexFlags` and `FutexOp`, as passed to the `futex` syscall.
19#[derive(Debug, Copy, Clone, Eq, PartialEq)]
20pub struct FutexOpAndFlags {
21    op: FutexOp,
22    flags: FutexFlags,
23}
24
25impl FutexOpAndFlags {
26    pub fn new(op: FutexOp, flags: FutexFlags) -> Self {
27        Self { op, flags }
28    }
29
30    pub fn op(&self) -> FutexOp {
31        self.op
32    }
33
34    pub fn flags(&self) -> FutexFlags {
35        self.flags
36    }
37}
38
39#[derive(Debug, Copy, Clone, Eq, PartialEq)]
40pub enum FutexOpAndFlagsTryFromI32Error {
41    UnknownOp(i32),
42}
43
44impl TryFrom<i32> for FutexOpAndFlags {
45    type Error = FutexOpAndFlagsTryFromI32Error;
46    fn try_from(value: i32) -> Result<Self, Self::Error> {
47        // Currently all operations fit in the lowest 4 bits. Assume any other bits are flags.
48        let op_mask = 0b1111;
49        let op_value = value & op_mask;
50        let op = FutexOp::try_from(op_value)
51            .map_err(|_| FutexOpAndFlagsTryFromI32Error::UnknownOp(op_value))?;
52        let flags = FutexFlags::from_bits_retain(value & !op_mask);
53        Ok(FutexOpAndFlags::new(op, flags))
54    }
55}
56
57impl From<FutexOpAndFlags> for i32 {
58    fn from(value: FutexOpAndFlags) -> Self {
59        i32::from(value.op) | value.flags.bits()
60    }
61}
62
63bitflags::bitflags! {
64    /// Flags that can contained in the `futex_op` parameter to the `futex` syscall.
65    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
66    pub struct FutexFlags: i32 {
67        const FUTEX_PRIVATE_FLAG = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_PRIVATE_FLAG);
68        const FUTEX_CLOCK_REALTIME = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_CLOCK_REALTIME);
69    }
70}
71
72/// Operations that can be specified in the `futex_op` parameter to the `futex` syscall.
73#[derive(
74    Debug, Copy, Clone, Eq, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive,
75)]
76#[repr(i32)]
77#[allow(non_camel_case_types)]
78pub enum FutexOp {
79    FUTEX_WAIT = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAIT),
80    FUTEX_WAKE = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAKE),
81    FUTEX_FD = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_FD),
82    FUTEX_REQUEUE = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_REQUEUE),
83    FUTEX_CMP_REQUEUE = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_CMP_REQUEUE),
84    FUTEX_WAKE_OP = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAKE_OP),
85    FUTEX_LOCK_PI = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_LOCK_PI),
86    FUTEX_UNLOCK_PI = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_UNLOCK_PI),
87    FUTEX_TRYLOCK_PI = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_TRYLOCK_PI),
88    FUTEX_WAIT_BITSET = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAIT_BITSET),
89    FUTEX_WAKE_BITSET = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAKE_BITSET),
90    FUTEX_WAIT_REQUEUE_PI = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_WAIT_REQUEUE_PI),
91    FUTEX_CMP_REQUEUE_PI = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_CMP_REQUEUE_PI),
92    FUTEX_LOCK_PI2 = const_conversions::i32_from_u32(bindings::LINUX_FUTEX_LOCK_PI2),
93}
94
95/// # Safety
96/// See futex(2). Pointers must be valid or NULL.
97pub unsafe fn futex_raw(
98    uaddr: *mut u32,
99    op: core::ffi::c_int,
100    val: u32,
101    utime: *const kernel_timespec,
102    uaddr2: *mut u32,
103    val3: u32,
104) -> Result<core::ffi::c_int, Errno> {
105    unsafe {
106        syscall!(
107            linux_syscall::SYS_futex,
108            uaddr,
109            op,
110            val,
111            utime,
112            uaddr2,
113            val3
114        )
115    }
116    .try_i64()
117    // the linux x86-64 syscall implementation returns an int so I don't think this should ever fail
118    .map(|x| x.try_into().expect("futex() returned invalid int"))
119    .map_err(Errno::from)
120}
121
122// I don't see any reason to mark this as "unsafe", but I didn't look through all of the possible
123// futex operations
124pub fn futex(
125    uaddr: &AtomicU32,
126    op: FutexOpAndFlags,
127    val: u32,
128    utime: Option<&kernel_timespec>,
129    uaddr2: Option<&AtomicU32>,
130    val3: u32,
131) -> Result<core::ffi::c_int, Errno> {
132    let utime = utime
133        .map(core::ptr::from_ref)
134        .unwrap_or(core::ptr::null_mut());
135    let uaddr2 = uaddr2
136        .map(AtomicU32::as_ptr)
137        .unwrap_or(core::ptr::null_mut());
138
139    unsafe { futex_raw(uaddr.as_ptr(), i32::from(op), val, utime, uaddr2, val3) }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    // miri doesn't support non-libc syscalls
147    #[cfg(not(miri))]
148    #[test]
149    fn test_futex_error() {
150        let rv = unsafe {
151            futex_raw(
152                core::ptr::null_mut(),
153                0,
154                0,
155                core::ptr::null(),
156                core::ptr::null_mut(),
157                0,
158            )
159        };
160
161        // check that errors are returned correctly even though it returns a signed integer
162        assert_eq!(rv, Err(Errno::EFAULT));
163    }
164}