From 0c1c0b975387835756b160d9dab3b471b5010a85 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Thu, 8 Dec 2022 18:31:20 -0500
Subject: [PATCH] helper: start xmrig with sudo, implement [restart_xmrig()]

This commit takes care of correctly starting [sudo] with XMRig as
a command argument. The frontend [restart_*] function is also
implemented. In XMRig's case, the threads will sleep [3/5 seconds]
before [starting/restarting] so that [sudo] has time to open its
STDIN. This prevents premature inputs and outputting the password
to the STDOUT.
---
 src/gupax.rs  |   2 +-
 src/helper.rs | 111 ++++++++++++++++++++++++++++++--------------------
 src/main.rs   |   8 ++--
 src/sudo.rs   |  21 ++++++----
 4 files changed, 84 insertions(+), 58 deletions(-)

diff --git a/src/gupax.rs b/src/gupax.rs
index 75b2b27..a20a4d5 100644
--- a/src/gupax.rs
+++ b/src/gupax.rs
@@ -232,7 +232,7 @@ impl Gupax {
 		// Saved [Tab]
 		ui.group(|ui| {
 			let height = ui.available_height()/2.0;
-			let width = (width/5.0)-(SPACE*1.8);
+			let width = (width/5.0)-(SPACE*1.93);
 			ui.horizontal(|ui| {
 			if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).on_hover_text(GUPAX_TAB_ABOUT).clicked() { self.tab = Tab::About; }
 			ui.separator();
diff --git a/src/helper.rs b/src/helper.rs
index 8fe76d1..7e5f83e 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -43,8 +43,11 @@ use std::{
 	time::*,
 	thread,
 };
+use crate::{
+	constants::*,
+	SudoState,
+};
 use serde::{Serialize,Deserialize};
-use crate::constants::*;
 use num_format::{Buffer,Locale};
 use log::*;
 
@@ -558,29 +561,32 @@ impl Helper {
 		helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
 	}
 
-//	// The "restart frontend" to a "frontend" function.
-//	// Basically calls to kill the current p2pool, waits a little, then starts the below function in a a new thread, then exit.
-//	pub fn restart_p2pool(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: &std::path::PathBuf) {
-//		info!("P2Pool | Attempting to restart...");
-//		helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Restart;
-//		helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
-//
-//		let helper = Arc::clone(&helper);
-//		let state = state.clone();
-//		let path = path.clone();
-//		// This thread lives to wait, start p2pool then die.
-//		thread::spawn(move || {
-//			while helper.lock().unwrap().p2pool.lock().unwrap().is_alive() {
-//				warn!("P2Pool | Want to restart but process is still alive, waiting...");
-//				thread::sleep(SECOND);
-//			}
-//			// Ok, process is not alive, start the new one!
-//			Self::start_p2pool(&helper, &state, &path);
-//		});
-//		info!("P2Pool | Restart ... OK");
-//	}
+	// The "restart frontend" to a "frontend" function.
+	// Basically calls to kill the current xmrig, waits a little, then starts the below function in a a new thread, then exit.
+	pub fn restart_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
+		info!("XMRig | Attempting to restart...");
+		helper.lock().unwrap().xmrig.lock().unwrap().signal = ProcessSignal::Restart;
+		helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
 
-	pub fn start_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) {
+		let helper = Arc::clone(&helper);
+		let state = state.clone();
+		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() {
+				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);
+		});
+		info!("XMRig | Restart ... OK");
+	}
+
+	pub fn start_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
 		helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
 
 		let args = Self::build_xmrig_args_and_mutate_img(helper, state, path);
@@ -596,7 +602,7 @@ impl Helper {
 		let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_xmrig);
 		let path = path.clone();
 		thread::spawn(move || {
-			Self::spawn_xmrig_watchdog(process, gui_api, pub_api, priv_api, args, path);
+			Self::spawn_xmrig_watchdog(process, gui_api, pub_api, priv_api, args, path, sudo);
 		});
 	}
 
@@ -606,6 +612,12 @@ impl Helper {
 	pub fn build_xmrig_args_and_mutate_img(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> Vec<String> {
 		let mut args = Vec::with_capacity(500);
 		let path = path.clone();
+		// The actual binary we're executing is [sudo], technically
+		// 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.
+		args.push(r#"--prompt="#.to_string());
+		args.push(path.display().to_string());
 
 		// [Simple]
 		if state.simple {
@@ -666,7 +678,7 @@ impl Helper {
 
 	// The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
 	#[tokio::main]
-	async fn spawn_xmrig_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubXmrigApi>>, pub_api: Arc<Mutex<PubXmrigApi>>, priv_api: Arc<Mutex<PrivXmrigApi>>, args: Vec<String>, mut path: std::path::PathBuf) {
+	async fn spawn_xmrig_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubXmrigApi>>, pub_api: Arc<Mutex<PubXmrigApi>>, priv_api: Arc<Mutex<PrivXmrigApi>>, args: Vec<String>, mut path: std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
 		// 1a. Create PTY
 		let pty = portable_pty::native_pty_system();
 		let pair = pty.openpty(portable_pty::PtySize {
@@ -676,12 +688,15 @@ impl Helper {
 			pixel_height: 0,
 		}).unwrap();
 		// 1b. Create command
-		let mut cmd = portable_pty::CommandBuilder::new(path.as_path());
+		let mut cmd = portable_pty::CommandBuilder::new("sudo");
 		cmd.args(args);
 		cmd.cwd(path.as_path().parent().unwrap());
 		// 1c. Create child
 		let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap()));
 
+		// 1d. Wait a bit for [sudo].
+		thread::sleep(std::time::Duration::from_secs(3));
+
         // 2. Set process state
         let mut lock = process.lock().unwrap();
         lock.state = ProcessState::Alive;
@@ -690,6 +705,11 @@ impl Helper {
 		lock.child = Some(Arc::clone(&child_pty));
 		let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
 		lock.stdin = Some(pair.master);
+
+		// 3. Input [sudo] pass, wipe, then drop.
+		writeln!(lock.stdin.as_mut().unwrap(), "{}", sudo.lock().unwrap().pass);
+		SudoState::wipe(&sudo);
+		drop(sudo);
 		drop(lock);
 
 		// 3. Spawn PTY read thread
@@ -712,37 +732,38 @@ impl Helper {
 			// Set timer
 			let now = Instant::now();
 
-			// Check SIGNAL
-			if process.lock().unwrap().signal == ProcessSignal::Stop {
+			// Stop on [Stop/Restart] SIGNAL
+			let signal = process.lock().unwrap().signal;
+			if signal == ProcessSignal::Stop || signal == ProcessSignal::Restart  {
 				child_pty.lock().unwrap().kill();
 				let exit_status = match child_pty.lock().unwrap().wait() {
 					Ok(e) => {
+						let mut process = process.lock().unwrap();
 						if e.success() {
-							process.lock().unwrap().state = ProcessState::Dead; "Successful"
+							if process.signal == ProcessSignal::Stop { process.state = ProcessState::Dead; }
+							"Successful"
 						} else {
-							process.lock().unwrap().state = ProcessState::Failed; "Failed"
+							if process.signal == ProcessSignal::Stop { process.state = ProcessState::Failed; }
+							"Failed"
 						}
 					},
-					_ => { process.lock().unwrap().state = ProcessState::Failed; "Unknown Error" },
+					_ => {
+						let mut process = process.lock().unwrap();
+						if process.signal == ProcessSignal::Stop { process.state = ProcessState::Failed; }
+						"Unknown Error"
+					},
 				};
 				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;
+				let mut process = process.lock().unwrap();
+				match process.signal {
+					ProcessSignal::Stop    => process.signal = ProcessSignal::None,
+					ProcessSignal::Restart => process.state = ProcessState::Waiting,
+					_ => (),
+				}
 				break
-			// Check RESTART
-			} else if process.lock().unwrap().signal == ProcessSignal::Restart {
-				child_pty.lock().unwrap().kill();
-				let exit_status = match child_pty.lock().unwrap().wait() {
-					Ok(e) => if e.success() { "Successful" } else { "Failed" },
-					_ => "Unknown Error",
-				};
-				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().state = ProcessState::Waiting;
-				break
-			// Check if the process is secretly died without us knowing :)
+			// 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" },
diff --git a/src/main.rs b/src/main.rs
index 3ebe5fd..1d9a9cb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -965,7 +965,7 @@ impl eframe::App for App {
 							if ui.add_sized([box_width, height], Button::new(RichText::new("👁").color(color))).on_hover_text(PASSWORD_HIDE).clicked() { sudo.hide = !sudo.hide; }
 						});
 						if esc || ui.add_sized([width, height*4.0], Button::new("Leave")).clicked() { self.error_state.reset(); };
-						// If [test_sudo()] finished, reset sudo state + error state.
+						// If [test_sudo()] finished, reset error state.
 						if sudo.success {
 							self.error_state.reset();
 						}
@@ -1164,8 +1164,8 @@ 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.error_state.ask_sudo(&self.sudo);
-//									Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path);
+									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);
@@ -1179,8 +1179,8 @@ impl eframe::App for App {
 									ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig");
 								});
 								if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig").clicked() {
+									self.sudo.lock().unwrap().signal = ProcessSignal::Start;
 									self.error_state.ask_sudo(&self.sudo);
-//									Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path);
 								}
 							}
 						});
diff --git a/src/sudo.rs b/src/sudo.rs
index 5eb7f68..235a02c 100644
--- a/src/sudo.rs
+++ b/src/sudo.rs
@@ -42,6 +42,7 @@ pub struct SudoState {
 	pub hide: bool, // Are we hiding the password?
 	pub msg: String, // The message shown to the user if unsuccessful
 	pub pass: String, // The actual password wrapped in a [SecretVec]
+	pub signal: ProcessSignal, // Main GUI will set this depending on if we want [Start] or [Restart]
 }
 
 impl SudoState {
@@ -52,6 +53,7 @@ impl SudoState {
 			hide: true,
 			msg: "".to_string(),
 			pass: String::with_capacity(256),
+			signal: ProcessSignal::None,
 		}
 	}
 
@@ -61,15 +63,15 @@ impl SudoState {
 		let mut state = state.lock().unwrap();
 		state.testing = false;
 		state.success = false;
+		state.signal = ProcessSignal::None;
 	}
 
 	// Swaps the pass with another 256-capacity String,
 	// zeroizes the old and drops it.
 	pub fn wipe(state: &Arc<Mutex<Self>>) {
 		let mut new = String::with_capacity(256);
-		let mut state = state.lock().unwrap();
 		// new is now == old, and vice-versa.
-		std::mem::swap(&mut new, &mut state.pass);
+		std::mem::swap(&mut new, &mut state.lock().unwrap().pass);
 		// we're wiping & dropping the old pass here.
 		new.zeroize();
 		std::mem::drop(new);
@@ -127,7 +129,6 @@ impl SudoState {
 					Ok(Some(code)) => if code.success() {
 						info!("Sudo | Password ... OK!");
 						state.lock().unwrap().success = true;
-						crate::helper::Helper::start_xmrig(&helper, &xmrig, &path);  //----------- Start XMRig
 						break
 					},
 					Ok(None) => {
@@ -143,12 +144,16 @@ impl SudoState {
 					},
 				}
 			}
-			let mut lock = state.lock().unwrap();
-			if !lock.success { lock.msg = "Incorrect password!".to_string(); }
-			drop(lock);
 			sudo.kill();
-			Self::wipe(&state);
-			state.lock().unwrap().testing = false;
+			if state.lock().unwrap().success {
+				match state.lock().unwrap().signal {
+					ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
+					_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
+				}
+			} else {
+				state.lock().unwrap().msg = "Incorrect password!".to_string();
+				Self::wipe(&state);
+			}
 		});
 	}
 }