From db6f1fac13dace9a3cc33d29ab96191101aecb5e Mon Sep 17 00:00:00 2001 From: hinto-janaiyo <hinto.janaiyo@protonmail.com> Date: Sat, 10 Dec 2022 20:55:44 -0500 Subject: [PATCH] macOS: handle killing XMRig with [sudo] Even with the parent-child relationship and direct process handle, Gupax can't kill an XMRig spawned with [sudo] on macOS, even though it can do it fine on Linux. So, on macOS, get the user's [sudo] pass on the [Stop] button and summon a [sudo kill] on XMRig's PID. This also complicates things since now we have to keep [SudoState] somehow between a [Stop] and a [Start]. So there is macOS specific code that now handles that. --- Cargo.lock | 11 +++++-- src/helper.rs | 88 ++++++++++++++++++++++++++++++++------------------- src/main.rs | 21 ++++++++---- src/sudo.rs | 4 ++- 4 files changed, 83 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 804bfa6..d4bce46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "rfd", "serde", "serde_json", + "static_vcruntime", "sudo", "tar", "tls-api", @@ -3740,6 +3741,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_vcruntime" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b" + [[package]] name = "str-buf" version = "1.0.6" @@ -3911,9 +3918,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17def29300a156c19ae30814710d9c63cd50288a49c6fd3a10ccfbe4cf886fd" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" dependencies = [ "flate2", "jpeg-decoder", diff --git a/src/helper.rs b/src/helper.rs index d7afae8..e620c72 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -23,12 +23,11 @@ // way down and (if possible) asynchronously executing them at the very end. // // The main GUI thread will interface with this thread by mutating the Arc<Mutex>'s -// found here, e.g: User clicks [Start P2Pool] -> Arc<Mutex<ProcessSignal> is set -// indicating to this thread during its loop: "I should start P2Pool!", e.g: +// found here, e.g: User clicks [Stop P2Pool] -> Arc<Mutex<ProcessSignal> is set +// indicating to this thread during its loop: "I should stop P2Pool!", e.g: // -// match p2pool.lock().unwrap().signal { -// ProcessSignal::Start => start_p2pool(), -// ... +// if p2pool.lock().unwrap().signal == ProcessSignal::Stop { +// stop_p2pool(), // } // // This also includes all things related to handling the child processes (P2Pool/XMRig): @@ -60,7 +59,7 @@ const MAX_PROCESS_OUTPUT_BYTES: usize = 56_000_000; // Just a little leeway so a reset will go off before the [String] allocates more memory. const PROCESS_OUTPUT_LEEWAY: usize = MAX_PROCESS_OUTPUT_BYTES - 1000; // The max bytes the GUI thread should hold -const MAX_GUI_OUTPUT_BYTES: usize = 1_000_000; +const MAX_GUI_OUTPUT_BYTES: usize = 500_000; const GUI_OUTPUT_LEEWAY: usize = MAX_GUI_OUTPUT_BYTES - 1000; @@ -234,7 +233,7 @@ impl Helper { } } - // For the GUI thread, the max is 1_000_000 bytes. + // For the GUI thread fn check_reset_gui_p2pool_output(gui_api: &Arc<Mutex<PubP2poolApi>>) { let mut gui_api = gui_api.lock().unwrap(); if gui_api.output.len() > GUI_OUTPUT_LEEWAY { @@ -554,6 +553,25 @@ impl Helper { // } // } // + // If processes are started with [sudo] on macOS, they must also + // be killed with [sudo] (even if I have a direct handle to it as the + // parent process...!). This is only needed on macOS, not Linux. + fn sudo_kill(pid: u32, sudo: &Arc<Mutex<SudoState>>) -> bool { + // Spawn [sudo] to execute [kill] on the given [pid] + let mut child = std::process::Command::new("sudo") + .args(["--stdin", "kill", "-9", &pid.to_string()]) + .stdin(Stdio::piped()) + .spawn().unwrap(); + + // Write the [sudo] password to STDIN. + let mut stdin = child.stdin.take().unwrap(); + use std::io::Write; + writeln!(stdin, "{}\n", sudo.lock().unwrap().pass); + + // Return exit code of [sudo/kill]. + child.wait().unwrap().success() + } + // Just sets some signals for the watchdog thread to pick up on. pub fn stop_xmrig(helper: &Arc<Mutex<Self>>) { info!("XMRig | Attempting to stop..."); @@ -573,13 +591,10 @@ impl Helper { let path = path.clone(); // This thread lives to wait, start xmrig then die. thread::spawn(move || { - while helper.lock().unwrap().xmrig.lock().unwrap().is_alive() { + while helper.lock().unwrap().xmrig.lock().unwrap().state != ProcessState::Waiting { warn!("XMRig | Want to restart but process is still alive, waiting..."); thread::sleep(SECOND); } - // We should reallllly sleep so all the components have a chance to catch up. - // Premature starting could miss the [sudo] STDIN timeframe and output it to STDOUT. - thread::sleep(std::time::Duration::from_secs(3)); // Ok, process is not alive, start the new one! Self::start_xmrig(&helper, &state, &path, sudo); }); @@ -616,10 +631,11 @@ impl Helper { // the XMRig path is just an argument to sudo, so add it. // Before that though, add the ["--prompt"] flag and set it // to emptyness so that it doesn't show up in the output. - #[cfg(target_family = "unix")] // Of course, only on Unix. - args.push(r#"--prompt="#.to_string()); - #[cfg(target_family = "unix")] - args.push(path.display().to_string()); + if cfg!(unix) { + args.push("--stdin".to_string()); + args.push(r#"--prompt="#.to_string()); + args.push(path.display().to_string()); + } // [Simple] if state.simple { @@ -716,10 +732,6 @@ impl Helper { // 1c. Create child let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap())); - // 1d. Wait a bit for [sudo]. - #[cfg(target_family = "unix")] - thread::sleep(std::time::Duration::from_secs(3)); - // 2. Set process state let mut lock = process.lock().unwrap(); lock.state = ProcessState::Alive; @@ -733,7 +745,6 @@ impl Helper { if cfg!(unix) { writeln!(lock.stdin.as_mut().unwrap(), "{}", sudo.lock().unwrap().pass); SudoState::wipe(&sudo); - drop(sudo); } drop(lock); @@ -757,10 +768,34 @@ impl Helper { // Set timer let now = Instant::now(); + // Check if the process secretly died without us knowing :) + if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() { + let exit_status = match code.success() { + true => { process.lock().unwrap().state = ProcessState::Dead; "Successful" }, + false => { process.lock().unwrap().state = ProcessState::Failed; "Failed" }, + }; + let uptime = HumanTime::into_human(start.elapsed()); + info!("XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status); + writeln!(gui_api.lock().unwrap().output, "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE); + process.lock().unwrap().signal = ProcessSignal::None; + break + } + // Stop on [Stop/Restart] SIGNAL let signal = process.lock().unwrap().signal; if signal == ProcessSignal::Stop || signal == ProcessSignal::Restart { - child_pty.lock().unwrap().kill(); + // macOS requires [sudo] again to kill [XMRig] + if cfg!(target_os = "macos") { + // If we're at this point, that means the user has + // entered their [sudo] pass again, after we wiped it. + // So, we should be able to find it in our [Arc<Mutex<SudoState>>]. + Self::sudo_kill(child_pty.lock().unwrap().process_id().unwrap(), &sudo); + // And... wipe it again (only if we're stopping full). + // If we're restarting, the next start will wipe it for us. + if signal != ProcessSignal::Restart { SudoState::wipe(&sudo); } + } else { + child_pty.lock().unwrap().kill(); + } let exit_status = match child_pty.lock().unwrap().wait() { Ok(e) => { let mut process = process.lock().unwrap(); @@ -788,17 +823,6 @@ impl Helper { _ => (), } break - // Check if the process secretly died without us knowing :) - } else if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() { - let exit_status = match code.success() { - true => { process.lock().unwrap().state = ProcessState::Dead; "Successful" }, - false => { process.lock().unwrap().state = ProcessState::Failed; "Failed" }, - }; - let uptime = HumanTime::into_human(start.elapsed()); - info!("XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status); - writeln!(gui_api.lock().unwrap().output, "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE); - process.lock().unwrap().signal = ProcessSignal::None; - break } // Check vector of user input diff --git a/src/main.rs b/src/main.rs index a636774..f45e321 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1207,14 +1207,23 @@ impl eframe::App for App { }); } else if self.xmrig.lock().unwrap().is_alive() { if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { - self.sudo.lock().unwrap().signal = ProcessSignal::Restart; - #[cfg(target_family = "unix")] - self.error_state.ask_sudo(&self.sudo); - #[cfg(target_os = "windows")] - Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); + if cfg!(windows) { + Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); + } else if cfg!(target_os = "macos") { + self.sudo.lock().unwrap().signal = ProcessSignal::Restart; + self.error_state.ask_sudo(&self.sudo); + } else { + self.sudo.lock().unwrap().signal = ProcessSignal::Restart; + self.error_state.ask_sudo(&self.sudo); + } } if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { - Helper::stop_xmrig(&self.helper); + if cfg!(target_os = "macos") { + self.sudo.lock().unwrap().signal = ProcessSignal::Stop; + self.error_state.ask_sudo(&self.sudo); + } else { + Helper::stop_xmrig(&self.helper); + } } ui.add_enabled_ui(false, |ui| { ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig"); diff --git a/src/sudo.rs b/src/sudo.rs index aafa3bf..1228c0f 100644 --- a/src/sudo.rs +++ b/src/sudo.rs @@ -78,7 +78,7 @@ impl SudoState { let mut state = state.lock().unwrap(); state.testing = false; state.success = false; - state.signal = ProcessSignal::None; +// state.signal = ProcessSignal::None; } // Swaps the pass with another 256-capacity String, @@ -163,12 +163,14 @@ impl SudoState { if state.lock().unwrap().success { match state.lock().unwrap().signal { ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)), + ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper), _ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)), } } else { state.lock().unwrap().msg = "Incorrect password!".to_string(); Self::wipe(&state); } + state.lock().unwrap().signal = ProcessSignal::None; }); } }