Skip to main content

starnix_modules_iouring/
syscalls.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::io_uring::{IORING_MAX_ENTRIES, IoUringFileObject};
6use starnix_core::mm::{IOVecPtr, MemoryAccessorExt};
7use starnix_core::security;
8use starnix_core::task::CurrentTask;
9use starnix_core::vfs::{FdFlags, FdNumber};
10use starnix_logging::track_stub;
11
12use starnix_syscalls::{SUCCESS, SyscallResult};
13use starnix_uapi::auth::CAP_SYS_ADMIN;
14use starnix_uapi::errors::Errno;
15use starnix_uapi::signals::SigSet;
16use starnix_uapi::user_address::{UserAddress, UserRef};
17use starnix_uapi::user_value::UserValue;
18use starnix_uapi::{
19    errno, error, io_uring_params,
20    io_uring_register_op_IORING_REGISTER_BUFFERS as IORING_REGISTER_BUFFERS,
21    io_uring_register_op_IORING_REGISTER_IOWQ_MAX_WORKERS as IORING_REGISTER_IOWQ_MAX_WORKERS,
22    io_uring_register_op_IORING_REGISTER_PBUF_RING as IORING_REGISTER_PBUF_RING,
23    io_uring_register_op_IORING_REGISTER_PBUF_STATUS as IORING_REGISTER_PBUF_STATUS,
24    io_uring_register_op_IORING_REGISTER_PERSONALITY as IORING_REGISTER_PERSONALITY,
25    io_uring_register_op_IORING_REGISTER_RING_FDS as IORING_REGISTER_RING_FDS,
26    io_uring_register_op_IORING_UNREGISTER_BUFFERS as IORING_UNREGISTER_BUFFERS,
27    io_uring_register_op_IORING_UNREGISTER_PBUF_RING as IORING_UNREGISTER_PBUF_RING,
28    io_uring_register_op_IORING_UNREGISTER_RING_FDS as IORING_UNREGISTER_RING_FDS, uapi,
29};
30use std::sync::atomic;
31
32pub fn sys_io_uring_setup(
33    current_task: &CurrentTask,
34    user_entries: UserValue<u32>,
35    user_params: UserRef<io_uring_params>,
36) -> Result<FdNumber, Errno> {
37    // TODO: https://fxbug.dev/397186254 - we will want to do a no-audit CAP_IPC_LOCK capability
38    // check; see "If not granted CAP_IPC_LOCK io_uring operations are accounted against the user's
39    // RLIMIT_MEMLOCK limit" at
40    // https://github.com/SELinuxProject/selinux-notebook/blob/main/src/auditing.md#capability-audit-exemptions
41
42    if !current_task.kernel().features.io_uring {
43        return error!(ENOSYS);
44    }
45
46    // Apply policy from /proc/sys/kernel/io_uring_disabled
47    let limits = &current_task.kernel().system_limits;
48    match limits.io_uring_disabled.load(atomic::Ordering::Relaxed) {
49        0 => (),
50        1 => {
51            let io_uring_group = limits.io_uring_group.load(atomic::Ordering::Relaxed).try_into();
52            if io_uring_group.is_err()
53                || !current_task.current_creds().is_in_group(io_uring_group.unwrap())
54            {
55                security::check_task_capable(current_task, CAP_SYS_ADMIN)?;
56            }
57        }
58        _ => {
59            return error!(EPERM);
60        }
61    }
62
63    let entries = user_entries.validate(1..IORING_MAX_ENTRIES).ok_or_else(|| errno!(EINVAL))?;
64
65    let mut params = current_task.read_object(user_params)?;
66    for byte in params.resv {
67        if byte != 0 {
68            return error!(EINVAL);
69        }
70    }
71
72    let file = IoUringFileObject::new_file(current_task, entries, &mut params)?;
73
74    // io_uring file descriptors are always created with CLOEXEC.
75    let fd = current_task.add_file(file, FdFlags::CLOEXEC)?;
76    current_task.write_object(user_params, &params)?;
77    Ok(fd)
78}
79
80pub fn sys_io_uring_enter(
81    current_task: &CurrentTask,
82    fd: FdNumber,
83    to_submit: u32,
84    min_complete: u32,
85    flags: u32,
86    _sig: UserRef<SigSet>,
87    sigset_size: usize,
88) -> Result<u32, Errno> {
89    if !current_task.kernel().features.io_uring {
90        return error!(ENOSYS);
91    }
92    if !_sig.is_null() {
93        if sigset_size != std::mem::size_of::<SigSet>() {
94            return error!(EINVAL);
95        }
96    }
97    let file = current_task.files().get(fd)?;
98    let io_uring = file.downcast_file::<IoUringFileObject>().ok_or_else(|| errno!(EOPNOTSUPP))?;
99    // TODO(https://fxbug.dev/297431387): Use `_sig` to change the signal mask for `current_task`.
100    io_uring.enter(current_task, to_submit, min_complete, flags)
101}
102
103pub fn sys_io_uring_register(
104    current_task: &CurrentTask,
105    fd: FdNumber,
106    opcode: u32,
107    arg: UserAddress,
108    nr_args: UserValue<u32>,
109) -> Result<SyscallResult, Errno> {
110    if !current_task.kernel().features.io_uring {
111        return error!(ENOSYS);
112    }
113    let file = current_task.files().get(fd)?;
114    let io_uring = file.downcast_file::<IoUringFileObject>().ok_or_else(|| errno!(EOPNOTSUPP))?;
115    match opcode {
116        IORING_REGISTER_BUFFERS => {
117            // TODO(https://fxbug.dev/297431387): Check nr_args for zero and return EINVAL here.
118            let iovec = IOVecPtr::new(current_task, arg);
119            let buffers = current_task.read_iovec(iovec, nr_args)?;
120            io_uring.register_buffers(buffers);
121            return Ok(SUCCESS);
122        }
123        IORING_UNREGISTER_BUFFERS => {
124            if !arg.is_null() {
125                return error!(EINVAL);
126            }
127            io_uring.unregister_buffers();
128            return Ok(SUCCESS);
129        }
130        IORING_REGISTER_IOWQ_MAX_WORKERS => {
131            track_stub!(
132                TODO("https://fxbug.dev/297431387"),
133                "io_uring_register IORING_REGISTER_IOWQ_MAX_WORKERS",
134                opcode
135            );
136            // The current implementation only ever use 1 worker for read and 1 for write.
137            return Ok(SUCCESS);
138        }
139        IORING_REGISTER_RING_FDS => {
140            track_stub!(
141                TODO("https://fxbug.dev/297431387"),
142                "io_uring_register IORING_REGISTER_RING_FDS",
143                opcode
144            );
145            // The current implementation doesn't use any thread local specific identifier for
146            // performance. Instead, when registering a fd, just return the passed fd as the value
147            // to use.
148            let nr_args: usize = nr_args.raw().try_into().map_err(|_| errno!(EINVAL))?;
149            if nr_args > 16 {
150                return error!(EINVAL);
151            }
152            let updates_addr = UserRef::<uapi::io_uring_rsrc_update>::from(arg);
153            let mut updates = current_task
154                .read_objects_to_smallvec::<uapi::io_uring_rsrc_update, 1>(updates_addr, nr_args)?;
155            let mut result = 0;
156            for update in updates.iter_mut() {
157                if update.offset == u32::MAX {
158                    update.offset = update.data.try_into().map_err(|_| errno!(EINVAL))?;
159                    result += 1;
160                }
161            }
162            current_task.write_objects(updates_addr, &updates)?;
163            return Ok(result.into());
164        }
165        IORING_UNREGISTER_RING_FDS => {
166            track_stub!(
167                TODO("https://fxbug.dev/297431387"),
168                "io_uring_register IORING_UNREGISTER_RING_FDS",
169                opcode
170            );
171            // Because registering a fd doesn't use any resource currently, unregistering is free.
172            return Ok(SUCCESS);
173        }
174        IORING_REGISTER_PBUF_RING => {
175            let nr_args: usize = nr_args.raw().try_into().map_err(|_| errno!(EINVAL))?;
176            if nr_args != 1 {
177                return error!(EINVAL);
178            }
179            let buffer_definition: uapi::io_uring_buf_reg = current_task.read_object(arg.into())?;
180            io_uring.register_ring_buffers(buffer_definition)?;
181            return Ok(SUCCESS);
182        }
183
184        IORING_UNREGISTER_PBUF_RING => {
185            let nr_args: usize = nr_args.raw().try_into().map_err(|_| errno!(EINVAL))?;
186            if nr_args != 1 {
187                return error!(EINVAL);
188            }
189            let buffer_definition: uapi::io_uring_buf_reg = current_task.read_object(arg.into())?;
190            io_uring.unregister_ring_buffers(buffer_definition)?;
191            return Ok(SUCCESS);
192        }
193
194        IORING_REGISTER_PBUF_STATUS => {
195            let nr_args: usize = nr_args.raw().try_into().map_err(|_| errno!(EINVAL))?;
196            if nr_args != 1 {
197                return error!(EINVAL);
198            }
199            let buffer_status_addr = UserRef::<uapi::io_uring_buf_status>::from(arg);
200            let mut buffer_status: uapi::io_uring_buf_status =
201                current_task.read_object(buffer_status_addr)?;
202            io_uring.ring_buffer_status(&mut buffer_status)?;
203            current_task.write_object(buffer_status_addr, &buffer_status)?;
204            return Ok(SUCCESS);
205        }
206        IORING_REGISTER_PERSONALITY => {
207            // TODO(https://fxbug.dev/505326006) If registering personality is implemented,
208            // then implement the uring_override_creds security hook.
209            track_stub!(
210                TODO("https://fxbug.dev/297431387"),
211                "io_uring_register unknown op",
212                opcode
213            );
214            return error!(EINVAL);
215        }
216        _ => {
217            track_stub!(
218                TODO("https://fxbug.dev/297431387"),
219                "io_uring_register unknown op",
220                opcode
221            );
222            return error!(EINVAL);
223        }
224    }
225}
226
227pub use sys_io_uring_enter as sys_arch32_io_uring_enter;
228pub use sys_io_uring_register as sys_arch32_io_uring_register;
229pub use sys_io_uring_setup as sys_arch32_io_uring_setup;