1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! This library implements basic support for running a command in an elevated context.
//!
//! In particular this runs a command through "sudo" or other platform equivalents.
//!
//! ## Basic Usage
//!
//! The library provides a single struct called `Command` which largely follows the
//! API of `std::process::Command`.  However it does not support capturing output or
//! gives any guarantees for the working directory or environment.  This is because
//! the platform APIs do not have support for that either in some cases.
//!
//! In particular the working directory is always the system32 folder on windows and
//! the environment variables are always the ones of the initial system session on
//! OS X if the GUI mode is used.
//!
//! ```rust,no_run
//! use runas::Command;
//!
//! let status = Command::new("rm")
//!     .arg("/usr/local/my-app")
//!     .status()
//!     .unwrap();
//! ```
//!
//! ## Platform Support
//!
//! The following platforms are supported:
//!
//! * Windows: always GUI mode
//! * OS X: GUI and CLI mode
//! * Linux: CLI mode

use std::ffi::{OsStr, OsString};
use std::io;
use std::process::{Child, ExitStatus};

#[cfg(target_os = "macos")]
mod impl_darwin;
#[cfg(unix)]
mod impl_unix;
#[cfg(windows)]
mod impl_windows;

/// A process builder for elevated execution, providing fine-grained control
/// over how a new process should be spawned.
///
/// A default configuration can be
/// generated using `Command::new(program)`, where `program` gives a path to the
/// program to be executed. Additional builder methods allow the configuration
/// to be changed (for example, by adding arguments) prior to spawning:
///
/// ```rust,no_run
/// use runas::Command;
///
/// let child = if cfg!(target_os = "windows") {
///     Command::new("cmd")
///             .args(&["/C", "echo hello"])
///             .spawn()
///             .expect("failed to execute process")
/// } else {
///     Command::new("sh")
///             .arg("-c")
///             .arg("echo hello")
///             .spawn()
///             .expect("failed to execute process")
/// };
///
/// let hello = child.wait();
/// ```
///
/// `Command` can be reused to spawn multiple processes. The builder methods
/// change the command without needing to immediately spawn the process.
///
/// ```rust,no_run
/// use runas::Command;
///
/// let mut echo_hello = Command::new("sh");
/// echo_hello.arg("-c")
///           .arg("echo hello");
/// let hello_1 = echo_hello.spawn().expect("failed to execute process");
/// let hello_2 = echo_hello.spawn().expect("failed to execute process");
/// ```
///
/// Similarly, you can call builder methods after spawning a process and then
/// spawn a new process with the modified settings.
///
/// ```rust,no_run
/// use runas::Command;
///
/// let mut list_dir = Command::new("ls");
///
/// // Execute `ls` in the current directory of the program.
/// list_dir.status().expect("process failed to execute");
///
/// println!();
///
/// // Change `ls` to execute in the root directory.
/// list_dir.current_dir("/");
///
/// // And then execute `ls` again but in the root directory.
/// list_dir.status().expect("process failed to execute");
/// ```
pub struct Command {
    command: OsString,
    args: Vec<OsString>,
    current_dir: Option<OsString>,
    force_prompt: bool,
    hide: bool,
    gui: bool,
}

impl Command {
    /// Constructs a new `Command` for launching the program at
    /// path `program`, with the following default configuration:
    ///
    /// * No arguments to the program
    /// * Program to be visable
    /// * Not launched from a GUI context
    /// * Inherit the current process's environment
    /// * Inherit the current process's working directory
    /// * Inherit stdin/stdout/stderr for `spawn` or `status`, but create pipes for `output`
    ///
    /// Builder methods are provided to change these defaults and
    /// otherwise configure the process.
    ///
    /// If `program` is not an absolute path, the `PATH` will be searched in
    /// an OS-defined way.
    ///
    /// The search path to be used may be controlled by setting the
    /// `PATH` environment variable on the Command,
    /// but this has some implementation limitations on Windows
    /// (see issue #37519).
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```rust,no_run
    /// use runas::Command;
    ///
    /// Command::new("sh")
    ///         .spawn()
    ///         .expect("sh command failed to start");
    /// ```
    pub fn new<S: AsRef<OsStr>>(program: S) -> Command {
        Command {
            command: program.as_ref().to_os_string(),
            args: vec![],
            current_dir: None,
            hide: false,
            gui: false,
            force_prompt: true,
        }
    }

    /// Adds an argument to pass to the program.
    ///
    /// Only one argument can be passed per use. So instead of:
    ///
    /// ```rust,no_run
    /// # runas::Command::new("sh")
    /// .arg("-C /path/to/repo")
    /// # ;
    /// ```
    ///
    /// usage would be:
    ///
    /// ```rust,no_run
    /// # runas::Command::new("sh")
    /// .arg("-C")
    /// .arg("/path/to/repo")
    /// # ;
    /// ```
    ///
    /// To pass multiple arguments see [`args`].
    ///
    /// [`args`]: Command::args
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```no_run
    /// use runas::Command;
    ///
    /// Command::new("ls")
    ///         .arg("-l")
    ///         .arg("-a")
    ///         .spawn()
    ///         .expect("ls command failed to start");
    /// ```
    pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
        self.args.push(arg.as_ref().to_os_string());
        self
    }

    /// Adds multiple arguments to pass to the program.
    ///
    /// To pass a single argument see [`arg`].
    ///
    /// [`arg`]: Command::arg
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```no_run
    /// use runas::Command;
    ///
    /// Command::new("ls")
    ///         .args(&["-l", "-a"])
    ///         .spawn()
    ///         .expect("ls command failed to start");
    /// ```
    pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Command {
        for arg in args {
            self.arg(arg);
        }
        self
    }

    /// Sets the working directory for the child process.
    ///
    /// # Platform-specific behavior
    ///
    /// If the program path is relative (e.g., `"./script.sh"`), it's ambiguous
    /// whether it should be interpreted relative to the parent's working
    /// directory or relative to `current_dir`. The behavior in this case is
    /// platform specific and unstable, and it's recommended to use
    /// [`canonicalize`] to get an absolute program path instead.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```no_run
    /// use runas::Command;
    ///
    /// Command::new("ls")
    ///         .current_dir("/bin")
    ///         .spawn()
    ///         .expect("ls command failed to start");
    /// ```
    ///
    /// [`canonicalize`]: std::fs::canonicalize
    pub fn current_dir<P: AsRef<std::path::Path>>(&mut self, dir: P) -> &mut Command {
        self.current_dir = Some(dir.as_ref().as_os_str().into());
        self
    }

    /// Controls the visibility of the program on supported platforms.
    /// 
    /// The default is to launch the program visible.
    /// 
    /// # Examples
    ///
    /// ```rust,no_run
    /// use runas::Command;
    ///
    /// let status = Command::new("/bin/cat")
    ///                      .arg("file.txt")
    ///                      .disable_prompt()
    ///                      .status()
    ///                      .expect("failed to execute process");
    ///
    /// assert!(status.success());
    /// ```
    pub fn show(&mut self, val: bool) -> &mut Command {
        self.hide = !val;
        self
    }

    /// Controls the GUI context.  The default behavior is to assume that the program is
    /// launched from a command line (not using a GUI).  This primarily controls how the
    /// elevation prompt is rendered.  On some platforms like Windows the elevation prompt
    /// is always a GUI element.
    ///
    /// If the preferred mode is not available it falls back to the other automatically.
    pub fn gui(&mut self, val: bool) -> &mut Command {
        self.gui = val;
        self
    }


    /// Disabling the force prompt would allow the successive use of elevated commands on unix platforms
    /// without prompting for a password after each command.
    /// 
    /// By default, the user will be prompted on each successive command.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use runas::Command;
    ///
    /// let status = Command::new("/bin/cat")
    ///                      .arg("file.txt")
    ///                      .disable_prompt()
    ///                      .status()
    ///                      .expect("failed to execute process");
    ///
    /// assert!(status.success());
    /// 
    /// //The user won't be prompted for a password on the second run.
    /// status = Command::new("/bin/ps")
    ///                      .disable_prompt()
    ///                      .status()
    ///                      .expect("failed to execute process");
    ///
    /// assert!(status.success());
    /// ```
    pub fn disable_force_prompt(&mut self) -> &mut Command {
        self.force_prompt = false;
        self
    }

    /// Executes the command as a child process, returning a handle to it.
    ///
    /// By default, stdin, stdout and stderr are inherited from the parent.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```no_run
    /// use runas::Command;
    ///
    /// Command::new("ls")
    ///         .spawn()
    ///         .expect("ls command failed to start");
    /// ```
    pub fn spawn(&mut self) -> io::Result<Child> {
        #[cfg(all(unix, target_os = "macos"))]
        use crate::impl_darwin::spawn_impl;
        #[cfg(all(unix, not(target_os = "macos")))]
        use impl_unix::spawn_impl;
        #[cfg(windows)]
        use impl_windows::spawn_impl;
        spawn_impl(&self)
    }

    /// Executes a command as a child process, waiting for it to finish and
    /// collecting its exit status.
    ///
    /// By default, stdin, stdout and stderr are inherited from the parent.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use runas::Command;
    ///
    /// let status = Command::new("/bin/cat")
    ///                      .arg("file.txt")
    ///                      .status()
    ///                      .expect("failed to execute process");
    ///
    /// println!("process exited with: {}", status);
    ///
    /// assert!(status.success());
    /// ```
    pub fn status(&mut self) -> io::Result<ExitStatus> {
        self.spawn()?.wait()
    }
}