1use crate::execution::loop_entry::enter_syscall_loop;
6use crate::ptrace::{PtraceCoreState, ptrace_attach_from_state};
7use crate::task::{CurrentTask, DelayedReleaser, ExitStatus, TaskBuilder, ZirconThread};
8use anyhow::Error;
9use starnix_logging::{log_error, log_warn};
10use starnix_sync::{
11 ExecutorVmarManagerLock, LockBefore, LockDepMutex, Locked, TaskRelease, Unlocked,
12};
13use starnix_uapi::errors::Errno;
14use starnix_uapi::{errno, error};
15use std::os::unix::thread::JoinHandleExt;
16use std::sync::Arc;
17use std::sync::mpsc::sync_channel;
18use thread_create_vmars::ThreadCreateVmars;
19
20struct ExecutorVmarManager(LockDepMutex<ThreadCreateVmars, ExecutorVmarManagerLock>);
24
25pub fn execute_task_with_prerun_result<L, F, R, G>(
26 locked: &mut Locked<L>,
27 task_builder: TaskBuilder,
28 pre_run: F,
29 task_complete: G,
30 ptrace_state: Option<PtraceCoreState>,
31) -> Result<R, Errno>
32where
33 L: LockBefore<TaskRelease>,
34 F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> Result<R, Errno> + Send + Sync + 'static,
35 R: Send + Sync + 'static,
36 G: FnOnce(Result<ExitStatus, Error>) + Send + Sync + 'static,
37{
38 let (sender, receiver) = sync_channel::<Result<R, Errno>>(1);
39 execute_task(
40 locked,
41 task_builder,
42 move |current_task, locked| match pre_run(current_task, locked) {
43 Err(errno) => {
44 let _ = sender.send(Err(errno.clone()));
45 Err(errno)
46 }
47 Ok(value) => sender.send(Ok(value)).map_err(|error| {
48 log_error!("Unable to send `pre_run` result: {error:?}");
49 errno!(EINVAL)
50 }),
51 },
52 task_complete,
53 ptrace_state,
54 )?;
55 receiver.recv().map_err(|e| {
56 log_error!("Unable to retrieve result from `pre_run`: {e:?}");
57 errno!(EINVAL)
58 })?
59}
60
61pub fn execute_task<L, F, G>(
62 locked: &mut Locked<L>,
63 task_builder: TaskBuilder,
64 pre_run: F,
65 task_complete: G,
66 ptrace_state: Option<PtraceCoreState>,
67) -> Result<(), Errno>
68where
69 L: LockBefore<TaskRelease>,
70 F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> Result<(), Errno> + Send + Sync + 'static,
71 G: FnOnce(Result<ExitStatus, Error>) + Send + Sync + 'static,
72{
73 let process_handle = task_builder.task.thread_group().process.raw_handle();
76
77 let kernel = task_builder.task.kernel();
78 let create_vmars = kernel
79 .expando
80 .get_or_init(|| ExecutorVmarManager(LockDepMutex::new(ThreadCreateVmars::new())));
81 let mut create_vmars = create_vmars.0.lock();
82
83 let old_handles = unsafe {
90 thrd_set_zx_create_handles(thrd_zx_create_handles {
91 process: process_handle,
92 machine_stack_vmar: create_vmars.machine_stack.probe()?.raw_handle(),
93 security_stack_vmar: create_vmars.security_stack.probe()?.raw_handle(),
94 thread_block_vmar: create_vmars.thread_block.probe()?.raw_handle(),
95 })
96 };
97 scopeguard::defer! {
98 unsafe {
103 thrd_set_zx_create_handles(old_handles);
104 };
105 };
106
107 if let Some(ptrace_state) = ptrace_state {
108 let _ = ptrace_attach_from_state(
109 locked.cast_locked::<TaskRelease>(),
110 &task_builder.task,
111 ptrace_state,
112 );
113 }
114
115 let ref_task = Arc::clone(&task_builder.task);
116 let running_state = ref_task.running_state().unwrap();
117
118 let (sender, receiver) = sync_channel::<TaskBuilder>(1);
121 let result = std::thread::Builder::new().name("user-thread".to_string()).spawn(move || {
122 #[allow(
124 clippy::undocumented_unsafe_blocks,
125 reason = "Force documented unsafe blocks in Starnix"
126 )]
127 let locked = unsafe { Unlocked::new() };
128
129 let mut current_task: CurrentTask = receiver
133 .recv()
134 .expect("caller should always send task builder before disconnecting")
135 .into();
136
137 std::mem::drop(receiver);
140
141 let pre_run_result = { pre_run(locked, &mut current_task) };
142 if pre_run_result.is_err() {
143 if current_task.exit_status().is_none() {
146 log_error!("Pre run failed from {pre_run_result:?}. The task will not be run.");
147 }
148
149 std::mem::drop(task_complete);
152 } else {
153 let exit_status = enter_syscall_loop(locked, &mut current_task);
154 current_task.write().set_exit_status(exit_status.clone());
155 task_complete(Ok(exit_status));
156 }
157
158 current_task.release(locked);
161
162 DelayedReleaser::finalize();
164 });
165 let join_handle = match result {
166 Ok(handle) => handle,
167 Err(e) => {
168 task_builder.release(locked);
169 match e.kind() {
170 std::io::ErrorKind::WouldBlock => return error!(EAGAIN),
171 other => panic!("unexpected error on thread spawn: {other}"),
172 }
173 }
174 };
175
176 task_builder.task.write().set_spawned();
178
179 let pthread = join_handle.as_pthread_t();
185 #[allow(
186 clippy::undocumented_unsafe_blocks,
187 reason = "Force documented unsafe blocks in Starnix"
188 )]
189 let raw_thread_handle =
190 unsafe { zx::Unowned::<'_, zx::Thread>::from_raw_handle(thrd_get_zx_handle(pthread)) };
191 let thread = Arc::new(
192 raw_thread_handle
193 .duplicate_handle(zx::Rights::SAME_RIGHTS)
194 .expect("must have RIGHT_DUPLICATE on handle we created"),
195 );
196 running_state.thread.set(ZirconThread::new(thread)).expect("thread should only be set once");
197 if let Err(err) = ref_task.sync_scheduler_state_to_role() {
199 log_warn!(err:?; "Couldn't update freshly spawned thread's profile.");
200 }
201
202 ref_task.record_pid_koid_mapping();
204
205 sender
209 .send(task_builder)
210 .expect("receiver should not be disconnected because thread spawned successfully");
211
212 Ok(())
213}
214
215#[repr(C)]
216#[derive(Debug)]
217pub struct thrd_zx_create_handles {
218 pub process: zx::sys::zx_handle_t,
219 pub machine_stack_vmar: zx::sys::zx_handle_t,
220 pub security_stack_vmar: zx::sys::zx_handle_t,
221 pub thread_block_vmar: zx::sys::zx_handle_t,
222}
223unsafe extern "C" {
224 fn thrd_set_zx_create_handles(handles: thrd_zx_create_handles) -> thrd_zx_create_handles;
225
226 fn thrd_get_zx_handle(thread: u64) -> zx::sys::zx_handle_t;
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::ptrace::StopState;
235 use crate::signals::SignalInfo;
236 use crate::testing::*;
237 use starnix_uapi::signals::{SIGCONT, SIGSTOP};
238
239 #[::fuchsia::test]
240 async fn test_block_if_stopped_stop_and_continue() {
241 spawn_kernel_and_run(async |locked, task| {
242 assert!(!task.block_if_stopped(locked));
244
245 task.thread_group().set_stopped(
247 StopState::GroupStopping,
248 Some(SignalInfo::kernel(SIGSTOP)),
249 false,
250 );
251
252 let thread = std::thread::spawn({
253 let task = task.weak_task();
254 move || {
255 let task = task.upgrade().expect("task must be alive");
256 while !task.read().is_blocked() {
258 std::thread::sleep(std::time::Duration::from_millis(10));
259 }
260
261 task.thread_group().set_stopped(
263 StopState::Waking,
264 Some(SignalInfo::kernel(SIGCONT)),
265 false,
266 );
267 }
268 });
269
270 assert!(task.block_if_stopped(locked));
272
273 thread.join().expect("joined");
275
276 assert!(!task.block_if_stopped(locked));
278 })
279 .await;
280 }
281
282 #[::fuchsia::test]
283 async fn test_block_if_stopped_stop_and_exit() {
284 spawn_kernel_and_run(async |locked, task| {
285 assert!(!task.block_if_stopped(locked));
287
288 task.thread_group().set_stopped(
290 StopState::GroupStopping,
291 Some(SignalInfo::kernel(SIGSTOP)),
292 false,
293 );
294
295 let thread = std::thread::spawn({
296 let task = task.weak_task();
297 move || {
298 #[allow(
299 clippy::undocumented_unsafe_blocks,
300 reason = "Force documented unsafe blocks in Starnix"
301 )]
302 let locked = unsafe { Unlocked::new() };
303 let task = task.upgrade().expect("task must be alive");
304 while !task.read().is_blocked() {
306 std::thread::sleep(std::time::Duration::from_millis(10));
307 }
308
309 task.thread_group().kill(locked, ExitStatus::Exit(1), None);
311 }
312 });
313
314 assert!(task.block_if_stopped(locked));
316
317 thread.join().expect("joined");
319
320 assert!(!task.block_if_stopped(locked));
322 })
323 .await;
324 }
325}