super_shell/lib.rs
1use std::io::{BufRead, BufReader, Write};
2
3use std::process::{Command, Stdio};
4
5/// Represents a shell with root privileges.
6pub struct RootShell {
7 shell_process: std::process::Child,
8}
9
10/// String that is appended to the end of each command, to indicate that the command has finished.
11const END_OF_COMMAND: &str = "~end-of-command~";
12
13/// Implementation of RootShell
14impl RootShell {
15 /// Creates a new root shell.
16 /// Defaults to pkexec as super user provider and sh as shell.
17 /// Returns None if the root shell could not be created.
18 /// # Example
19 /// ```
20 /// use super_shell::RootShell;
21 /// let mut root_shell = RootShell::new().expect("Failed to crate root shell");
22 /// println!("{}", root_shell.execute("echo Hello $USER"));
23 /// ```
24 #[cfg(target_os = "linux")]
25 pub fn new() -> Option<Self> {
26 Self::spawn_root_shell("pkexec", "sh")
27 }
28
29 #[cfg(target_os = "windows")]
30 pub fn new() -> Option<Self> {
31 None
32 }
33
34 #[cfg(target_os = "windows")]
35 pub fn new_custom(super_user_provider: &str, shell: &str) -> Option<Self> {
36 None
37 }
38
39 /// Creates a new root shell with the specified super user provider and shell.
40 /// Returns None if the root shell could not be created.
41 /// sudo as an interactive super user provider is currently not supported.
42 /// # Parameter
43 /// * `super_user_provider` - The command to use to get super user privileges
44 /// * `shell` - The shell to use
45 /// # Example
46 /// ```
47 /// use super_shell::RootShell;
48 /// let mut root_shell = RootShell::new_custom("gksu", "bash").expect("Failed to crate root shell");
49 /// println!("{}", root_shell.execute("echo Hello $USER"));
50 /// ```
51 #[cfg(target_os = "linux")]
52 pub fn new_custom(super_user_provider: &str, shell: &str) -> Option<Self> {
53 Self::spawn_root_shell(super_user_provider, shell)
54 }
55
56 /// Creates a new root shell with the specified super user provider and shell.
57 /// Returns None if the root shell could not be created.
58 /// # Parameter
59 /// * `super_user_provider` - The command to use to get super user privileges
60 /// * `shell` - The shell to use
61 fn spawn_root_shell(super_user_provider: &str, shell: &str) -> Option<RootShell> {
62 let shell_process = Command::new(super_user_provider)
63 .arg(shell)
64 .stdin(Stdio::piped())
65 .stdout(Stdio::piped())
66 .stderr(Stdio::inherit())
67 .spawn()
68 .unwrap();
69
70 let mut root_shell = Self { shell_process };
71
72 // Make sure we are root now
73 let user = root_shell.execute("whoami");
74 if !user.trim().eq("root") {
75 return None;
76 }
77
78 Some(root_shell)
79 }
80
81 /// Executes a command in the root shell and returns the output trimmed.
82 /// Blocks the current thread until the command is finished.
83 /// # Parameter
84 /// * `command` - The command to execute
85 /// # Example
86 /// ```
87 /// use super_shell::RootShell;
88 /// let mut root_shell = RootShell::new().expect("Failed to crate root shell");
89 /// assert!(root_shell.execute("echo Hello $USER").trim().eq("Hello root"));
90 /// ```
91 pub fn execute(&mut self, command: impl AsRef<str>) -> String {
92 // If not linux return empty string
93 #[cfg(not(target_os = "linux"))]
94 return "".to_string();
95
96 // Append end of command string to the command
97 let command = command.as_ref().to_string();
98
99 // Write the actual command to stdin of the root shell
100 let mut shell_stdin = self.shell_process.stdin.as_mut().unwrap();
101 writeln!(&mut shell_stdin, "{}", command).unwrap();
102 shell_stdin.flush().unwrap();
103
104 // Write "end of command" string to stdin of the root shell
105 writeln!(&mut shell_stdin, "echo {}", END_OF_COMMAND).unwrap();
106 shell_stdin.flush().unwrap();
107
108 // Read piped stdout from the root shell
109 let stdout = self.shell_process.stdout.as_mut().unwrap();
110 let mut stdout_reader = BufReader::new(stdout);
111
112 // Read until the "end of command" string is found
113 let mut string_data = String::new();
114 loop {
115 let mut line = String::new();
116 stdout_reader.read_line(&mut line).unwrap();
117 string_data.push_str(&line);
118
119 if line.contains(END_OF_COMMAND) {
120 break;
121 }
122 }
123
124 // Clean up the string
125 let cmd_response = string_data.replace(END_OF_COMMAND, "");
126 let cmd_response = cmd_response.trim().to_string();
127
128 cmd_response
129 }
130
131 /// Exits the root shell and waits for the process to finish.
132 pub fn exit(&mut self) {
133 let mut shell_stdin = self.shell_process.stdin.as_mut().unwrap();
134 writeln!(&mut shell_stdin, "exit").unwrap();
135 shell_stdin.flush().unwrap();
136 self.shell_process.wait().expect("failed to wait on child");
137 }
138}
139
140/// Drop implementation for RootShell.
141impl Drop for RootShell {
142 /// Exits the root shell and waits for the process to finish.
143 fn drop(&mut self) {
144 self.exit();
145 self.shell_process.wait().expect("failed to wait on child");
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::RootShell;
152
153 #[test]
154 fn test_custom() {
155 let mut root_shell = RootShell::new_custom("sudo", "bash").unwrap();
156 assert!(root_shell
157 .execute("echo Hello $USER")
158 .trim()
159 .eq("Hello root"));
160 }
161}