tcrm_task/tasks/process/group/action/
pause.rs

1use crate::tasks::process::group::builder::ProcessGroup;
2use crate::tasks::process::group::error::ProcessGroupError;
3
4impl ProcessGroup {
5    /// Pauses all processes in the group (Unix).
6    ///
7    /// Sends SIGSTOP to the process group using killpg().
8    ///
9    /// # Returns
10    ///
11    /// * `Ok(())` - If the signal was sent successfully or processes were already stopped
12    /// * `Err(ProcessGroupError)` - If pausing fails due to permissions or other errors
13    ///
14    /// # Example
15    /// ```rust,no_run
16    /// use tcrm_task::tasks::process::group::builder::ProcessGroup;
17    /// let mut group = ProcessGroup::new();
18    /// // ... spawn processes in the group ...
19    /// group.pause_group().unwrap();
20    /// ```
21    #[cfg(unix)]
22    pub fn pause_group(&self) -> Result<(), ProcessGroupError> {
23        use nix::sys::signal::{Signal, killpg};
24        use nix::unistd::Pid;
25        if let Some(pgid) = self.inner.process_group_id {
26            use nix::errno::Errno;
27            match killpg(Pid::from_raw(pgid), Signal::SIGSTOP) {
28                Ok(_) => Ok(()),
29                Err(e) => match e {
30                    Errno::ESRCH => Ok(()), // Already stopped/terminated
31                    Errno::EPERM => Err(ProcessGroupError::SignalFailed(format!(
32                        "Permission denied to pause process group {}",
33                        pgid
34                    ))),
35                    _ => Err(ProcessGroupError::SignalFailed(format!(
36                        "Failed to send SIGSTOP to process group {}: {}",
37                        pgid, e
38                    ))),
39                },
40            }
41        } else {
42            Err(ProcessGroupError::SignalFailed(
43                "No process group ID available".to_string(),
44            ))
45        }
46    }
47
48    /// Pauses all processes in the job object (Windows).
49    ///
50    /// Suspends all processes in the job object by iterating through them.
51    ///
52    /// # Returns
53    ///
54    /// * `Ok(())` - If the processes were suspended successfully
55    /// * `Err(ProcessGroupError)` - If pausing fails due to permissions or other errors
56    ///
57    /// # Example
58    /// ```rust,no_run
59    /// use tcrm_task::tasks::process::group::builder::ProcessGroup;
60    /// let mut group = ProcessGroup::new();
61    /// // ... spawn processes in the group ...
62    /// group.pause_group().unwrap();
63    /// ```
64    #[cfg(windows)]
65    pub fn pause_group(&self) -> Result<(), ProcessGroupError> {
66        use crate::tasks::process::group::builder::SendHandle;
67        use windows::Win32::Foundation::CloseHandle;
68        use windows::Win32::System::Diagnostics::ToolHelp::{
69            CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next,
70        };
71        use windows::Win32::System::JobObjects::{
72            JOBOBJECT_BASIC_PROCESS_ID_LIST, JobObjectBasicProcessIdList, QueryInformationJobObject,
73        };
74        use windows::Win32::System::Threading::{OpenThread, SuspendThread, THREAD_SUSPEND_RESUME};
75
76        if let Some(SendHandle(job_handle)) = &self.inner.job_handle {
77            unsafe {
78                // First, get the list of processes in the job
79                let mut process_list = JOBOBJECT_BASIC_PROCESS_ID_LIST::default();
80                let mut returned_length = 0u32;
81
82                QueryInformationJobObject(
83                    Some(*job_handle),
84                    JobObjectBasicProcessIdList,
85                    &mut process_list as *mut _ as *mut std::ffi::c_void,
86                    std::mem::size_of::<JOBOBJECT_BASIC_PROCESS_ID_LIST>() as u32,
87                    Some(&mut returned_length),
88                )
89                .map_err(|e| {
90                    ProcessGroupError::SignalFailed(format!(
91                        "Failed to query job object process list: {}",
92                        e
93                    ))
94                })?;
95
96                let mut suspended_count = 0;
97
98                // Suspend all threads in each process
99                for i in 0..process_list.NumberOfProcessIdsInList {
100                    let pid = process_list.ProcessIdList[i as usize] as u32;
101
102                    // Take a snapshot of all threads in the system
103                    let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).map_err(|e| {
104                        ProcessGroupError::SignalFailed(format!(
105                            "Failed to create thread snapshot: {}",
106                            e
107                        ))
108                    })?;
109
110                    let mut thread_entry = THREADENTRY32 {
111                        dwSize: std::mem::size_of::<THREADENTRY32>() as u32,
112                        ..Default::default()
113                    };
114
115                    // Iterate through all threads and suspend those belonging to this process
116                    if Thread32First(snapshot, &mut thread_entry).is_ok() {
117                        loop {
118                            if thread_entry.th32OwnerProcessID == pid {
119                                let thread_handle = OpenThread(
120                                    THREAD_SUSPEND_RESUME,
121                                    false,
122                                    thread_entry.th32ThreadID,
123                                );
124                                if let Ok(handle) = thread_handle {
125                                    SuspendThread(handle);
126                                    let _ = CloseHandle(handle);
127                                    suspended_count += 1;
128                                }
129                            }
130
131                            if Thread32Next(snapshot, &mut thread_entry).is_err() {
132                                break;
133                            }
134                        }
135                    }
136
137                    let _ = CloseHandle(snapshot);
138                }
139
140                if suspended_count > 0 {
141                    Ok(())
142                } else {
143                    Err(ProcessGroupError::SignalFailed(
144                        "No threads were suspended in the job object".to_string(),
145                    ))
146                }
147            }
148        } else {
149            Err(ProcessGroupError::SignalFailed(
150                "No Job Object handle available".to_string(),
151            ))
152        }
153    }
154
155    /// Process group pausing is not available on this platform.
156    #[cfg(not(any(unix, windows)))]
157    pub fn pause_group(&self) -> Result<(), ProcessGroupError> {
158        Err(ProcessGroupError::UnsupportedPlatform(
159            "Process group pausing not available on this platform".to_string(),
160        ))
161    }
162}