shadow_rs/host/syscall/handler/
wait.rs
1use std::cmp;
2use std::ffi::c_int;
3
4use linux_api::errno::Errno;
5use linux_api::posix_types::kernel_pid_t;
6use linux_api::resource::rusage;
7use linux_api::signal::{Signal, siginfo_t};
8use linux_api::wait::{WaitFlags, WaitId};
9use shadow_shim_helper_rs::syscall_types::ForeignPtr;
10
11use crate::host::process::{ExitStatus, Process, ProcessId};
12use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
13use crate::host::syscall::types::SyscallError;
14
15enum WaitTarget {
16 Pid(ProcessId),
17 PidFd(#[allow(dead_code)] c_int),
18 Pgid(ProcessId),
19 Any,
20}
21
22impl WaitTarget {
23 pub fn matches(&self, process: &Process) -> bool {
24 match self {
25 WaitTarget::Pid(pid) => process.id() == *pid,
26 WaitTarget::PidFd(_) => unimplemented!(),
27 WaitTarget::Pgid(pgid) => process.group_id() == *pgid,
28 WaitTarget::Any => true,
29 }
30 }
31
32 pub fn from_waitpid_pid(current_process: &Process, pid: kernel_pid_t) -> Self {
33 match pid.cmp(&0) {
44 cmp::Ordering::Less => {
45 if pid == -1 {
46 WaitTarget::Any
47 } else {
48 WaitTarget::Pgid((-pid).try_into().unwrap())
49 }
50 }
51 cmp::Ordering::Equal => WaitTarget::Pgid(current_process.group_id()),
52 cmp::Ordering::Greater => WaitTarget::Pid(pid.try_into().unwrap()),
53 }
54 }
55
56 pub fn from_waitid(
57 current_process: &Process,
58 wait_id: WaitId,
59 pid: kernel_pid_t,
60 ) -> Option<Self> {
61 match wait_id {
62 WaitId::P_ALL => Some(WaitTarget::Any),
63 WaitId::P_PID => ProcessId::try_from(pid).ok().map(WaitTarget::Pid),
64 WaitId::P_PGID => {
65 let pgid = if pid == 0 {
66 Some(current_process.group_id())
67 } else {
68 ProcessId::try_from(pid).ok()
69 };
70 pgid.map(WaitTarget::Pgid)
71 }
72 WaitId::P_PIDFD => Some(WaitTarget::PidFd(pid)),
73 }
74 }
75}
76
77impl SyscallHandler {
78 fn wait_internal(
79 ctx: &mut SyscallContext,
80 target: WaitTarget,
81 status_ptr: ForeignPtr<c_int>,
82 infop: ForeignPtr<siginfo_t>,
83 options: WaitFlags,
84 usage: ForeignPtr<rusage>,
85 ) -> Result<kernel_pid_t, SyscallError> {
86 let processes = ctx.objs.host.processes_borrow();
87 let matching_children = processes.iter().filter(|(_pid, process)| {
88 let process = process.borrow(ctx.objs.host.root());
89 if process.parent_id() != ctx.objs.process.id() || !target.matches(&process) {
90 return false;
91 }
92 if options.contains(WaitFlags::__WNOTHREAD) {
93 warn_once_then_debug!("__WNOTHREAD unimplemented; ignoring.");
95 }
96 let is_clone_child = process.exit_signal() != Some(Signal::SIGCHLD);
97 if options.contains(WaitFlags::__WALL) {
98 true
99 } else if options.contains(WaitFlags::__WCLONE) {
100 is_clone_child
101 } else {
102 !is_clone_child
103 }
104 });
105 let mut matching_children = matching_children.peekable();
106 if matching_children.peek().is_none() {
107 return Err(Errno::ECHILD.into());
112 }
113
114 if !options.contains(WaitFlags::WEXITED) {
115 warn_once_then_debug!(
116 "Waiting only for child events that currently never happen under Shadow: {options:?}"
117 );
118 return if options.contains(WaitFlags::WNOHANG) {
122 Ok(0)
123 } else {
124 Err(SyscallError::new_blocked_on_child(
125 true,
126 ))
127 };
128 }
129
130 let mut matching_child_zombies = matching_children.filter(|(_pid, process)| {
131 let process = process.borrow(ctx.objs.host.root());
132 let zombie = process.borrow_as_zombie();
133 zombie.is_some()
134 });
135 let Some((matching_child_zombie_pid, matching_child_zombie)) =
136 matching_child_zombies.next()
137 else {
138 return if options.contains(WaitFlags::WNOHANG) {
140 Ok(0)
141 } else {
142 Err(SyscallError::new_blocked_on_child(
146 true,
147 ))
148 };
149 };
150
151 let zombie_process = matching_child_zombie.borrow(ctx.objs.host.root());
152 let zombie = zombie_process.borrow_as_zombie().unwrap();
153 let mut memory = ctx.objs.process.memory_borrow_mut();
154
155 if !status_ptr.is_null() {
156 let status = match zombie.exit_status() {
157 ExitStatus::Normal(i) => i << 8,
158 ExitStatus::Signaled(s) => {
159 i32::from(s)
162 }
163 ExitStatus::StoppedByShadow => unreachable!(),
164 };
165 memory.write(status_ptr, &status)?;
166 }
167 if !infop.is_null() {
168 let info = zombie.exit_siginfo(Signal::SIGCHLD);
169 memory.write(infop, &info)?;
170 }
171 if !usage.is_null() {
172 memory.write(usage, &ctx.objs.process.rusage())?;
173 }
174
175 let matching_child_zombie_pid: ProcessId = *matching_child_zombie_pid;
176 drop(memory);
178 drop(zombie);
179 drop(zombie_process);
180 drop(processes);
181
182 if !options.contains(WaitFlags::WNOWAIT) {
183 let zombie_process = ctx
184 .objs
185 .host
186 .process_remove(matching_child_zombie_pid)
187 .unwrap();
188 zombie_process.explicit_drop_recursive(ctx.objs.host.root(), ctx.objs.host);
189 }
190
191 Ok(matching_child_zombie_pid.into())
192 }
193
194 log_syscall!(
195 wait4,
196 kernel_pid_t,
197 kernel_pid_t,
198 *const c_int,
199 c_int,
200 *const std::ffi::c_void,
201 );
202 pub fn wait4(
203 ctx: &mut SyscallContext,
204 pid: kernel_pid_t,
205 status: ForeignPtr<c_int>,
206 options: c_int,
207 usage: ForeignPtr<rusage>,
208 ) -> Result<kernel_pid_t, SyscallError> {
209 let Some(mut wait_flags) = WaitFlags::from_bits(options) else {
210 return Err(Errno::EINVAL.into());
211 };
212
213 let allowed_flags = WaitFlags::WNOHANG
214 | WaitFlags::WUNTRACED
215 | WaitFlags::WCONTINUED
216 | WaitFlags::__WCLONE
217 | WaitFlags::__WALL
218 | WaitFlags::__WNOTHREAD;
219 let unexpected_flags = wait_flags.difference(allowed_flags);
220 if !unexpected_flags.is_empty() {
221 log::debug!("Unexpected flags: {unexpected_flags:?}");
224 return Err(Errno::EINVAL.into());
225 }
226
227 wait_flags |= WaitFlags::WEXITED;
229
230 let target = WaitTarget::from_waitpid_pid(ctx.objs.process, pid);
231 Self::wait_internal(ctx, target, status, ForeignPtr::null(), wait_flags, usage)
232 }
233
234 log_syscall!(
235 waitid,
236 kernel_pid_t,
237 c_int,
238 kernel_pid_t,
239 *const std::ffi::c_void,
240 c_int,
241 *const std::ffi::c_void,
242 );
243 pub fn waitid(
244 ctx: &mut SyscallContext,
245 which: c_int,
246 upid: kernel_pid_t,
247 infop: ForeignPtr<siginfo_t>,
248 options: c_int,
249 uru: ForeignPtr<rusage>,
250 ) -> Result<(), SyscallError> {
251 let wait_flags = WaitFlags::from_bits_retain(options);
252 let wait_id = WaitId::try_from(which).map_err(|_| Errno::EINVAL)?;
253 let Some(target) = WaitTarget::from_waitid(ctx.objs.process, wait_id, upid) else {
254 log::debug!("Invalid `which`+`upid` combination: {wait_id:?}:{upid}");
258 return Err(Errno::ECHILD.into());
259 };
260
261 Self::wait_internal(ctx, target, ForeignPtr::null(), infop, wait_flags, uru).map(|_| ())
262 }
263}