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        // From `waitpid(2)`:
34        // The value of pid can be:
35        // < -1  meaning wait for any child process whose process group ID is
36        //       equal to the absolute value of pid.
37        // -1    meaning wait for any child process.
38        // 0     meaning  wait  for any child process whose process group ID is
39        //       equal to that of the calling process at the time of the call to
40        //       waitpid().
41        // > 0   meaning wait for the child whose process ID is equal to the
42        //       value of pid.
43        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                // TODO: track parent thread and check it here.
94                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            // `waitpid(2)`:
108            // ECHILD: The process specified by pid (waitpid()) or idtype and id
109            // (waitid()) does not exist or is not  a  child  of  the calling
110            // process.
111            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            // The other events that can be waited for (WUNTRACED, WSTOPPED,
119            // WCONTINUED) currently can't happen under Shadow.
120            // TODO: If and when those things *can* happen, check for them here.
121            return if options.contains(WaitFlags::WNOHANG) {
122                Ok(0)
123            } else {
124                Err(SyscallError::new_blocked_on_child(
125                    /* restartable */ 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            // There are matching children, but none are zombies yet.
139            return if options.contains(WaitFlags::WNOHANG) {
140                Ok(0)
141            } else {
142                // FIXME: save `target` in SyscallCondition and reuse, in case
143                // the target was specified as 0 => "current process group id"
144                // and the process group changes in the meantime.
145                Err(SyscallError::new_blocked_on_child(
146                    /* restartable */ 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                    // This should be `| 0x80` if the process dumped core, but since
160                    // this depends on the system config we never set this flag.
161                    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 our borrow of the process list so that we can reap without a runtime borrow error.
177        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        /* rv */ kernel_pid_t,
197        /* pid */ kernel_pid_t,
198        /* status */ *const c_int,
199        /* options */ c_int,
200        /* usage */ *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            // These flags aren't permitted according to the `wait(2)`. We could
222            // support them here, but conservatively disallow.
223            log::debug!("Unexpected flags: {unexpected_flags:?}");
224            return Err(Errno::EINVAL.into());
225        }
226
227        // WEXITED is implicit for this syscall.
228        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        /* rv */ kernel_pid_t,
237        /* which */ c_int,
238        /* upid */ kernel_pid_t,
239        /* infop */ *const std::ffi::c_void,
240        /* options */ c_int,
241        /* uru */ *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            // We can get here if e.g. the ID was P_PID, but the pid was
255            // negative so couldn't be converted to a ProcessId. Afaict from the man page,
256            // this would simply result in no child matching the target, hence `ECHILD`.
257            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}