1mod scan;
33
34pub use scan::{
35 find_processes, sweep_matching, MatchSweepOptions, MatchSweepReport, MatchedProcess,
36 ProcessQuery,
37};
38
39use std::{
40 io,
41 num::NonZeroU32,
42 path::PathBuf,
43 thread,
44 time::{Duration, Instant, SystemTime},
45};
46
47#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
51pub struct StartToken {
52 pub major: u64,
53 pub minor: u64,
54}
55
56#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct AdoptedIdentity {
60 pid: NonZeroU32,
61 start: StartToken,
62 exe: Option<PathBuf>,
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub enum IdentityCheck {
68 Same,
70 Gone,
72 Replaced,
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub enum AdoptedSignal {
80 Graceful,
82 Force,
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89enum Delivery {
90 Graceful,
91 Force,
92 Stop,
93 Continue,
94}
95
96impl From<AdoptedSignal> for Delivery {
97 fn from(signal: AdoptedSignal) -> Self {
98 match signal {
99 AdoptedSignal::Graceful => Self::Graceful,
100 AdoptedSignal::Force => Self::Force,
101 }
102 }
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub enum SignalResult {
108 Delivered,
110 Gone,
112 IdentityMismatch,
114}
115
116#[derive(Clone, Debug, Default, Eq, PartialEq)]
118pub struct SweepReport {
119 pub terminated: Vec<u32>,
121 pub gone: Vec<u32>,
123 pub refused: Vec<u32>,
125 pub survivors: Vec<u32>,
127 pub failed: Vec<u32>,
129}
130
131impl AdoptedIdentity {
132 pub fn capture(pid: NonZeroU32) -> io::Result<Option<Self>> {
136 Ok(sys::read(pid.get())?.map(|seen| Self {
137 pid,
138 start: seen.start,
139 exe: seen.exe,
140 }))
141 }
142
143 pub fn from_recorded(pid: NonZeroU32, start: StartToken, exe: Option<PathBuf>) -> Self {
146 Self { pid, start, exe }
147 }
148
149 pub fn pid(&self) -> u32 {
150 self.pid.get()
151 }
152
153 pub fn start_token(&self) -> StartToken {
154 self.start
155 }
156
157 pub fn exe(&self) -> Option<&std::path::Path> {
158 self.exe.as_deref()
159 }
160
161 fn matches(&self, seen: &sys::Seen) -> bool {
162 seen.start == self.start
167 }
168
169 pub fn from_recorded_unix_ms(
176 pid: NonZeroU32,
177 recorded_unix_ms: u64,
178 tolerance: Duration,
179 ) -> io::Result<Option<Self>> {
180 let Some(current) = Self::capture(pid)? else {
181 return Ok(None);
182 };
183 let Some(started) = current.start_time() else {
184 return Ok(None);
185 };
186 let Some(recorded) =
187 SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(recorded_unix_ms))
188 else {
189 return Ok(None);
190 };
191 let gap = match started.duration_since(recorded) {
192 Ok(gap) => gap,
193 Err(early) => early.duration(),
194 };
195 Ok((gap <= tolerance).then_some(current))
196 }
197
198 pub fn start_time(&self) -> Option<SystemTime> {
202 sys::start_time(self.start)
203 }
204
205 pub fn verify(&self) -> io::Result<IdentityCheck> {
207 Ok(match sys::read(self.pid())? {
208 None => IdentityCheck::Gone,
209 Some(seen) if self.matches(&seen) => IdentityCheck::Same,
210 Some(_) => IdentityCheck::Replaced,
211 })
212 }
213
214 pub fn signal(&self, signal: AdoptedSignal) -> io::Result<SignalResult> {
217 if self.pid() == std::process::id() {
218 return Err(io::Error::new(
219 io::ErrorKind::InvalidInput,
220 "refusing to signal this process",
221 ));
222 }
223 sys::signal(self, signal.into())
224 }
225
226 pub fn suspend(&self) -> io::Result<SignalResult> {
229 self.deliver(Delivery::Stop)
230 }
231
232 pub fn resume(&self) -> io::Result<SignalResult> {
235 self.deliver(Delivery::Continue)
236 }
237
238 fn deliver(&self, delivery: Delivery) -> io::Result<SignalResult> {
239 if self.pid() == std::process::id() {
240 return Err(io::Error::new(
241 io::ErrorKind::InvalidInput,
242 "refusing to signal this process",
243 ));
244 }
245 sys::signal(self, delivery)
246 }
247
248 pub fn descendants(&self) -> io::Result<Vec<AdoptedIdentity>> {
253 if self.verify()? != IdentityCheck::Same {
254 return Ok(Vec::new());
255 }
256 let pairs = sys::process_pairs()?;
257 let mut found: Vec<AdoptedIdentity> = Vec::new();
258 let mut frontier = vec![(self.pid(), self.start)];
260 let mut next = 0;
261 while next < frontier.len() {
262 let (parent, parent_start) = frontier[next];
263 next += 1;
264 for &(child, claimed_parent) in &pairs {
265 if claimed_parent != parent
266 || child == parent
267 || child == self.pid()
268 || frontier.iter().any(|(pid, _)| *pid == child)
269 {
270 continue;
271 }
272 let (Some(pid), Ok(Some(seen))) = (NonZeroU32::new(child), sys::read(child)) else {
273 continue;
274 };
275 if seen.parent.is_some_and(|actual| actual != parent) || seen.start < parent_start {
276 continue; }
278 frontier.push((child, seen.start));
279 found.push(AdoptedIdentity {
280 pid,
281 start: seen.start,
282 exe: seen.exe,
283 });
284 }
285 }
286 if self.verify()? != IdentityCheck::Same {
288 return Ok(Vec::new());
289 }
290 Ok(found)
291 }
292}
293
294const SWEEP_POLL: Duration = Duration::from_millis(20);
295const FORCE_WAIT: Duration = Duration::from_millis(500);
296
297pub fn sweep(targets: &[AdoptedIdentity], grace: Duration) -> io::Result<SweepReport> {
303 let mut report = SweepReport::default();
304 let mut pending: Vec<&AdoptedIdentity> = Vec::new();
305 for target in targets {
306 match target.signal(AdoptedSignal::Graceful) {
307 Ok(SignalResult::Delivered) => pending.push(target),
308 Ok(SignalResult::Gone) => report.gone.push(target.pid()),
309 Ok(SignalResult::IdentityMismatch) => report.refused.push(target.pid()),
310 Err(_) => report.failed.push(target.pid()),
311 }
312 }
313 drain(&mut pending, grace, &mut report)?;
314 let mut forced: Vec<&AdoptedIdentity> = Vec::new();
315 for target in pending {
316 match target.signal(AdoptedSignal::Force) {
317 Ok(SignalResult::Delivered) => forced.push(target),
318 Ok(SignalResult::Gone) => report.terminated.push(target.pid()),
319 Ok(SignalResult::IdentityMismatch) => report.refused.push(target.pid()),
320 Err(_) => report.failed.push(target.pid()),
321 }
322 }
323 drain(&mut forced, FORCE_WAIT, &mut report)?;
324 report
325 .survivors
326 .extend(forced.iter().map(|target| target.pid()));
327 Ok(report)
328}
329
330pub fn sweep_descendants(root: &AdoptedIdentity, grace: Duration) -> io::Result<SweepReport> {
332 let mut targets = root.descendants()?;
333 targets.reverse();
334 sweep(&targets, grace)
335}
336
337#[cfg(unix)]
341pub fn suspend_current_process() -> io::Result<()> {
342 if unsafe { libc::raise(libc::SIGTSTP) } == 0 {
344 Ok(())
345 } else {
346 Err(io::Error::last_os_error())
347 }
348}
349
350#[cfg(not(unix))]
352pub fn suspend_current_process() -> io::Result<()> {
353 Err(io::Error::from(io::ErrorKind::Unsupported))
354}
355
356#[cfg(unix)]
358fn unix_signal_number(delivery: Delivery) -> libc::c_int {
359 match delivery {
360 Delivery::Graceful => libc::SIGTERM,
361 Delivery::Force => libc::SIGKILL,
362 Delivery::Stop => libc::SIGSTOP,
363 Delivery::Continue => libc::SIGCONT,
364 }
365}
366
367#[cfg(unix)]
371pub(crate) fn session_groups(sid: NonZeroU32) -> io::Result<Vec<libc::pid_t>> {
372 let wanted = sid.get() as libc::pid_t;
373 let own_group = unsafe { libc::getpgrp() };
375 let mut groups: Vec<libc::pid_t> = Vec::new();
376 for pid in sys::list_pids()? {
377 let pid = pid as libc::pid_t;
378 if unsafe { libc::getsid(pid) } != wanted {
380 continue;
381 }
382 let group = unsafe { libc::getpgid(pid) };
383 if group > 0 && group != own_group && !groups.contains(&group) {
384 groups.push(group);
385 }
386 }
387 Ok(groups)
388}
389
390#[cfg(unix)]
396pub fn signal_session(sid: NonZeroU32, signal: AdoptedSignal) -> io::Result<bool> {
397 signal_session_with(sid, signal.into())
398}
399
400#[cfg(unix)]
403pub fn suspend_session(sid: NonZeroU32) -> io::Result<bool> {
404 signal_session_with(sid, Delivery::Stop)
405}
406
407#[cfg(unix)]
410pub fn resume_session(sid: NonZeroU32) -> io::Result<bool> {
411 signal_session_with(sid, Delivery::Continue)
412}
413
414#[cfg(unix)]
415fn signal_session_with(sid: NonZeroU32, delivery: Delivery) -> io::Result<bool> {
416 let number = unix_signal_number(delivery);
417 let mut reached = false;
418 for group in session_groups(sid)? {
419 if unsafe { libc::kill(-group, number) } == 0 {
421 reached = true;
422 continue;
423 }
424 let error = io::Error::last_os_error();
425 if error.raw_os_error() != Some(libc::ESRCH) {
426 return Err(error);
427 }
428 }
429 Ok(reached)
430}
431
432fn drain(
435 pending: &mut Vec<&AdoptedIdentity>,
436 wait: Duration,
437 report: &mut SweepReport,
438) -> io::Result<()> {
439 let deadline = Instant::now().checked_add(wait);
440 loop {
441 let mut still = Vec::new();
442 for target in pending.drain(..) {
443 if target.verify()? == IdentityCheck::Same {
444 still.push(target);
445 } else {
446 report.terminated.push(target.pid());
447 }
448 }
449 *pending = still;
450 let expired = deadline.is_none_or(|limit| Instant::now() >= limit);
451 if pending.is_empty() || expired {
452 return Ok(());
453 }
454 thread::sleep(SWEEP_POLL);
455 }
456}
457
458mod sys {
459 use super::StartToken;
460 #[cfg(not(windows))]
461 use super::{AdoptedIdentity, SignalResult};
462 #[cfg(not(windows))]
463 use std::io;
464 use std::path::PathBuf;
465
466 pub(super) struct Seen {
468 pub(super) start: StartToken,
469 pub(super) exe: Option<PathBuf>,
470 pub(super) parent: Option<u32>,
473 }
474
475 #[cfg(target_os = "macos")]
477 pub(super) fn start_time(start: StartToken) -> Option<std::time::SystemTime> {
478 let micros = u32::try_from(start.minor).ok()?;
480 std::time::SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::new(
481 start.major,
482 micros.checked_mul(1000)?,
483 ))
484 }
485
486 #[cfg(any(target_os = "linux", target_os = "android"))]
488 pub(super) fn start_time(start: StartToken) -> Option<std::time::SystemTime> {
489 let hertz = u64::try_from(unsafe { libc::sysconf(libc::_SC_CLK_TCK) }).ok()?;
491 if hertz == 0 {
492 return None;
493 }
494 let boot = std::fs::read_to_string("/proc/stat")
495 .ok()?
496 .lines()
497 .find_map(|line| line.strip_prefix("btime "))
498 .and_then(|value| value.trim().parse::<u64>().ok())?;
499 let since_boot = std::time::Duration::new(
500 start.major / hertz,
501 u32::try_from((start.major % hertz) * 1_000_000_000 / hertz).ok()?,
502 );
503 std::time::SystemTime::UNIX_EPOCH
504 .checked_add(std::time::Duration::from_secs(boot))?
505 .checked_add(since_boot)
506 }
507
508 #[cfg(all(
509 unix,
510 not(any(target_os = "macos", target_os = "linux", target_os = "android"))
511 ))]
512 pub(super) fn start_time(_start: StartToken) -> Option<std::time::SystemTime> {
513 None
514 }
515
516 #[cfg(windows)]
518 pub(super) fn start_time(start: StartToken) -> Option<std::time::SystemTime> {
519 const EPOCH_OFFSET_TICKS: u64 = 116_444_736_000_000_000;
521 let since_unix = start.major.checked_sub(EPOCH_OFFSET_TICKS)?;
522 std::time::SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::new(
523 since_unix / 10_000_000,
524 u32::try_from((since_unix % 10_000_000) * 100).ok()?,
525 ))
526 }
527
528 #[cfg(not(any(unix, windows)))]
529 pub(super) fn start_time(_start: StartToken) -> Option<std::time::SystemTime> {
530 None
531 }
532
533 #[cfg(unix)]
534 pub(super) fn read(pid: u32) -> io::Result<Option<Seen>> {
535 Ok(crate::identity::describe(pid)?.map(|identity| Seen {
536 start: StartToken {
537 major: identity.started_sec,
538 minor: identity.started_usec,
539 },
540 exe: exe_path(pid),
541 parent: Some(identity.parent),
542 }))
543 }
544
545 #[cfg(target_os = "macos")]
546 fn exe_path(pid: u32) -> Option<PathBuf> {
547 use std::os::unix::ffi::OsStringExt;
548 let mut buffer = vec![0u8; 4096];
550 let written = unsafe {
552 libc::proc_pidpath(
553 pid as libc::c_int,
554 buffer.as_mut_ptr().cast(),
555 buffer.len() as u32,
556 )
557 };
558 if written <= 0 {
559 return None;
560 }
561 buffer.truncate(written as usize);
562 Some(PathBuf::from(std::ffi::OsString::from_vec(buffer)))
563 }
564
565 #[cfg(any(target_os = "linux", target_os = "android"))]
566 fn exe_path(pid: u32) -> Option<PathBuf> {
567 std::fs::read_link(format!("/proc/{pid}/exe")).ok()
568 }
569
570 #[cfg(all(
571 unix,
572 not(any(target_os = "macos", target_os = "linux", target_os = "android"))
573 ))]
574 fn exe_path(_pid: u32) -> Option<PathBuf> {
575 None
576 }
577
578 #[cfg(unix)]
579 pub(super) fn signal(
580 identity: &AdoptedIdentity,
581 signal: super::Delivery,
582 ) -> io::Result<SignalResult> {
583 use super::IdentityCheck;
584 match identity.verify()? {
585 IdentityCheck::Gone => return Ok(SignalResult::Gone),
586 IdentityCheck::Replaced => return Ok(SignalResult::IdentityMismatch),
587 IdentityCheck::Same => {}
588 }
589 let pid = i32::try_from(identity.pid()).map_err(io::Error::other)?;
590 let number = super::unix_signal_number(signal);
591 if unsafe { libc::kill(pid, number) } == 0 {
594 return Ok(SignalResult::Delivered);
595 }
596 let error = io::Error::last_os_error();
597 match error.raw_os_error() {
598 Some(libc::ESRCH) => Ok(SignalResult::Gone),
599 _ => Err(error),
600 }
601 }
602
603 #[cfg(unix)]
605 pub(super) fn process_pairs() -> io::Result<Vec<(u32, u32)>> {
606 let mut pairs = Vec::new();
607 for pid in list_pids()? {
608 if let Ok(Some(identity)) = crate::identity::describe(pid) {
609 pairs.push((pid, identity.parent));
610 }
611 }
612 Ok(pairs)
613 }
614
615 #[cfg(target_os = "macos")]
616 pub(super) fn list_pids() -> io::Result<Vec<u32>> {
617 const PROC_ALL_PIDS: u32 = 1;
619 let size = std::mem::size_of::<libc::pid_t>();
620 let needed = unsafe { libc::proc_listpids(PROC_ALL_PIDS, 0, std::ptr::null_mut(), 0) };
622 if needed <= 0 {
623 return Err(io::Error::last_os_error());
624 }
625 let mut pids = vec![0 as libc::pid_t; needed as usize / size + 64];
627 let written = unsafe {
629 libc::proc_listpids(
630 PROC_ALL_PIDS,
631 0,
632 pids.as_mut_ptr().cast(),
633 (pids.len() * size) as libc::c_int,
634 )
635 };
636 if written <= 0 {
637 return Err(io::Error::last_os_error());
638 }
639 pids.truncate(written as usize / size);
640 Ok(pids
641 .into_iter()
642 .filter(|pid| *pid > 0)
643 .map(|pid| pid as u32)
644 .collect())
645 }
646
647 #[cfg(any(target_os = "linux", target_os = "android"))]
648 pub(super) fn list_pids() -> io::Result<Vec<u32>> {
649 Ok(std::fs::read_dir("/proc")?
650 .flatten()
651 .filter_map(|entry| entry.file_name().to_str()?.parse::<u32>().ok())
652 .collect())
653 }
654
655 #[cfg(all(
656 unix,
657 not(any(target_os = "macos", target_os = "linux", target_os = "android"))
658 ))]
659 pub(super) fn list_pids() -> io::Result<Vec<u32>> {
660 Err(io::Error::from(io::ErrorKind::Unsupported))
661 }
662
663 #[cfg(windows)]
664 mod windows_impl {
665 use super::super::{AdoptedIdentity, SignalResult, StartToken};
666 use super::Seen;
667 use crate::process_windows::os_error;
668 use std::{io, path::PathBuf};
669 use windows::{
670 core::PWSTR,
671 Win32::{
672 Foundation::{CloseHandle, ERROR_INVALID_PARAMETER, FILETIME, HANDLE},
673 System::{
674 Diagnostics::ToolHelp::{
675 CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
676 TH32CS_SNAPPROCESS,
677 },
678 Threading::{
679 GetProcessTimes, OpenProcess, QueryFullProcessImageNameW, TerminateProcess,
680 WaitForSingleObject, PROCESS_ACCESS_RIGHTS, PROCESS_NAME_WIN32,
681 PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE,
682 },
683 },
684 },
685 };
686
687 struct Owned(HANDLE);
689 impl Drop for Owned {
690 fn drop(&mut self) {
691 let _ = unsafe { CloseHandle(self.0) };
693 }
694 }
695
696 fn open(pid: u32, access: PROCESS_ACCESS_RIGHTS) -> io::Result<Option<Owned>> {
697 match unsafe { OpenProcess(access, false, pid) } {
699 Ok(handle) => Ok(Some(Owned(handle))),
700 Err(error) if error.code() == ERROR_INVALID_PARAMETER.to_hresult() => Ok(None),
701 Err(error) => Err(os_error(error)),
702 }
703 }
704
705 fn observe(process: &Owned) -> io::Result<Option<Seen>> {
707 if unsafe { WaitForSingleObject(process.0, 0) }.0 == 0 {
709 return Ok(None);
710 }
711 let (mut created, mut exited, mut kernel, mut user) = (
712 FILETIME::default(),
713 FILETIME::default(),
714 FILETIME::default(),
715 FILETIME::default(),
716 );
717 unsafe {
719 GetProcessTimes(process.0, &mut created, &mut exited, &mut kernel, &mut user)
720 }
721 .map_err(os_error)?;
722 let ticks =
723 (u64::from(created.dwHighDateTime) << 32) | u64::from(created.dwLowDateTime);
724 let mut buffer = vec![0u16; 32768];
725 let mut length = buffer.len() as u32;
726 let exe = unsafe {
728 QueryFullProcessImageNameW(
729 process.0,
730 PROCESS_NAME_WIN32,
731 PWSTR(buffer.as_mut_ptr()),
732 &mut length,
733 )
734 }
735 .ok()
736 .map(|()| PathBuf::from(String::from_utf16_lossy(&buffer[..length as usize])));
737 Ok(Some(Seen {
738 start: StartToken {
739 major: ticks,
740 minor: 0,
741 },
742 exe,
743 parent: None,
744 }))
745 }
746
747 pub(in super::super) fn read(pid: u32) -> io::Result<Option<Seen>> {
748 let access = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE;
749 match open(pid, access)? {
750 Some(process) => observe(&process),
751 None => Ok(None),
752 }
753 }
754
755 pub(in super::super) fn signal(
756 identity: &AdoptedIdentity,
757 signal: super::super::Delivery,
758 ) -> io::Result<SignalResult> {
759 if matches!(
760 signal,
761 super::super::Delivery::Stop | super::super::Delivery::Continue
762 ) {
763 return Err(io::Error::from(io::ErrorKind::Unsupported));
765 }
766 let access =
767 PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE | PROCESS_TERMINATE;
768 let Some(process) = open(identity.pid(), access)? else {
769 return Ok(SignalResult::Gone);
770 };
771 let Some(seen) = observe(&process)? else {
773 return Ok(SignalResult::Gone);
774 };
775 if !identity.matches(&seen) {
776 return Ok(SignalResult::IdentityMismatch);
777 }
778 unsafe { TerminateProcess(process.0, 1) }.map_err(os_error)?;
780 Ok(SignalResult::Delivered)
781 }
782
783 pub(in super::super) fn process_pairs() -> io::Result<Vec<(u32, u32)>> {
784 let snapshot = Owned(
786 unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }.map_err(os_error)?,
787 );
788 let mut entry: PROCESSENTRY32W = unsafe { std::mem::zeroed() };
790 entry.dwSize = std::mem::size_of::<PROCESSENTRY32W>() as u32;
791 unsafe { Process32FirstW(snapshot.0, &mut entry) }.map_err(os_error)?;
793 let mut pairs = Vec::new();
794 loop {
795 pairs.push((entry.th32ProcessID, entry.th32ParentProcessID));
796 if unsafe { Process32NextW(snapshot.0, &mut entry) }.is_err() {
798 break;
799 }
800 }
801 Ok(pairs)
802 }
803 }
804
805 #[cfg(windows)]
806 pub(super) use windows_impl::{process_pairs, read, signal};
807
808 #[cfg(not(any(unix, windows)))]
809 pub(super) fn read(_pid: u32) -> io::Result<Option<Seen>> {
810 Err(io::Error::from(io::ErrorKind::Unsupported))
811 }
812 #[cfg(not(any(unix, windows)))]
813 pub(super) fn signal(_: &AdoptedIdentity, _: super::Delivery) -> io::Result<SignalResult> {
814 Err(io::Error::from(io::ErrorKind::Unsupported))
815 }
816 #[cfg(not(any(unix, windows)))]
817 pub(super) fn process_pairs() -> io::Result<Vec<(u32, u32)>> {
818 Err(io::Error::from(io::ErrorKind::Unsupported))
819 }
820}
821
822#[cfg(all(test, unix))]
823mod suspend_resume_tests {
824 use super::{AdoptedIdentity, SignalResult};
825 use std::{
826 num::NonZeroU32,
827 process::{Command, Stdio},
828 thread,
829 time::Duration,
830 };
831
832 fn state(pid: u32) -> String {
833 let out = Command::new("ps")
834 .args(["-o", "stat=", "-p", &pid.to_string()])
835 .output()
836 .expect("ps");
837 String::from_utf8_lossy(&out.stdout).trim().to_string()
838 }
839
840 fn wait_for(pid: u32, stopped: bool) -> bool {
841 for _ in 0..100 {
842 if state(pid).starts_with('T') == stopped {
843 return true;
844 }
845 thread::sleep(Duration::from_millis(20));
846 }
847 false
848 }
849
850 #[test]
851 fn suspend_then_resume_stops_and_continues_the_identified_process() {
852 let mut child = Command::new("sleep")
853 .arg("30")
854 .stdin(Stdio::null())
855 .spawn()
856 .expect("spawn sleep");
857 let pid = NonZeroU32::new(child.id()).unwrap();
858 let identity = AdoptedIdentity::capture(pid)
859 .expect("capture")
860 .expect("identity");
861 assert_eq!(identity.suspend().unwrap(), SignalResult::Delivered);
862 assert!(wait_for(pid.get(), true), "process did not stop");
863 assert_eq!(identity.resume().unwrap(), SignalResult::Delivered);
864 assert!(wait_for(pid.get(), false), "process did not continue");
865 child.kill().expect("kill");
866 let _ = child.wait();
867 assert_eq!(identity.suspend().unwrap(), SignalResult::Gone);
868 }
869
870 #[test]
871 fn suspend_and_resume_refuse_this_process() {
872 let pid = NonZeroU32::new(std::process::id()).unwrap();
873 let identity = AdoptedIdentity::capture(pid)
874 .expect("capture")
875 .expect("identity");
876 assert_eq!(
877 identity.suspend().unwrap_err().kind(),
878 std::io::ErrorKind::InvalidInput
879 );
880 assert_eq!(
881 identity.resume().unwrap_err().kind(),
882 std::io::ErrorKind::InvalidInput
883 );
884 }
885}