Wrap whitespace in arguments

Fixes #82
Fixes #87
This commit is contained in:
Félix Saparelli 2018-08-22 00:13:45 +12:00
parent 39a4a52f80
commit 21d4080183
3 changed files with 60 additions and 31 deletions

View File

@ -5,7 +5,7 @@ use clap::{App, Arg, Error};
#[derive(Debug)]
pub struct Args {
pub cmd: String,
pub cmd: Vec<String>,
pub paths: Vec<String>,
pub filters: Vec<String>,
pub ignores: Vec<String>,
@ -120,9 +120,7 @@ pub fn get_args() -> Args {
.arg(Arg::with_name("once").short("1").hidden(true))
.get_matches();
let cmd = values_t!(args.values_of("command"), String)
.unwrap()
.join(" ");
let cmd: Vec<String> = values_t!(args.values_of("command"), String).unwrap();
let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]);
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)

View File

@ -2,12 +2,48 @@ use std::path::PathBuf;
use std::collections::{HashMap, HashSet};
use pathop::PathOp;
pub fn spawn(cmd: &str, updated_paths: Vec<PathOp>, no_shell: bool) -> Process {
pub fn spawn(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Process {
self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process")
}
pub use self::imp::Process;
fn has_whitespace(s: &String) -> bool {
s.contains(|ch| match ch {
' ' => true,
'\t' => true,
_ => false
})
}
#[cfg(target_family = "unix")]
fn wrap_in_quotes(s: &String) -> String {
format!("'{}'", if s.contains('\'') {
s.replace('\'', "'\"'\"'")
} else {
s.clone()
})
}
#[cfg(target_family = "windows")]
fn wrap_in_quotes(s: &String) -> String {
format!("\"{}\"", if s.contains('"') {
s.replace('"', "\"\"")
} else {
s.clone()
})
}
fn wrap_commands(cmd: &Vec<String>) -> Vec<String> {
cmd.iter().map(|fragment| {
if has_whitespace(fragment) {
wrap_in_quotes(fragment)
} else {
fragment.clone()
}
}).collect()
}
#[cfg(target_family = "unix")]
mod imp {
use nix::{self, Error};
@ -15,6 +51,7 @@ mod imp {
use std::io::{self, Result};
use std::process::Command;
use std::sync::*;
use super::wrap_commands;
use signal::Signal;
use pathop::PathOp;
@ -35,7 +72,7 @@ mod imp {
#[allow(unknown_lints)]
#[allow(mutex_atomic)]
impl Process {
pub fn new(cmd: &str, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> {
pub fn new(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> {
use nix::unistd::*;
use std::os::unix::process::CommandExt;
@ -45,13 +82,13 @@ mod imp {
// but is a little less performant and can cause trouble when using custom signals
// (e.g. --signal SIGHUP)
let mut command = if no_shell {
let mut split = cmd.split_whitespace();
let mut command = Command::new(split.next().unwrap());
command.args(split);
let (head, tail) = cmd.split_first().unwrap();
let mut command = Command::new(head);
command.args(tail);
command
} else {
let mut command = Command::new("sh");
command.arg("-c").arg(cmd);
command.arg("-c").arg(wrap_commands(cmd).join(" "));
command
};
@ -134,6 +171,7 @@ mod imp {
use std::mem;
use std::process::Command;
use std::ptr;
use super::wrap_commands;
use kernel32::*;
use winapi::*;
use signal::Signal;
@ -151,7 +189,7 @@ mod imp {
}
impl Process {
pub fn new(cmd: &str, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> {
pub fn new(cmd: &Vec<String>, updated_paths: Vec<PathOp>, no_shell: bool) -> Result<Process> {
use std::os::windows::io::IntoRawHandle;
use std::os::windows::process::CommandExt;
@ -194,23 +232,17 @@ mod imp {
panic!("failed to set job info: {}", last_err());
}
let mut command;
if no_shell {
let (arg0, args) = cmd.split_first().unwrap();
command = Command::new(arg0);
command.args(args);
} else {
command = Command::new("cmd.exe");
command.arg("/C");
command.arg(wrap_commands(cmd).join(" "));
}
let mut iter_args = cmd.split_whitespace();
let arg0 = match no_shell {
true => iter_args.next().unwrap(),
false => "cmd.exe",
};
// TODO: There might be a better way of doing this with &str.
// I've had to fall back to String, as I wasn't able to join(" ") a Vec<&str>
// into a &str
let args: Vec<String> = match no_shell {
true => iter_args.map(str::to_string).collect(),
false => vec!["/C".to_string(), iter_args.collect::<Vec<&str>>().join(" ")],
};
let mut command = Command::new(arg0);
command.args(args);
command.creation_flags(CREATE_SUSPENDED);
debug!("Assembled command {:?}", command);
@ -397,7 +429,7 @@ mod tests {
#[test]
fn test_start() {
let _ = spawn("echo hi", vec![], true);
let _ = spawn(&vec!["echo".into(), "hi".into()], vec![], true);
}
#[test]

View File

@ -30,7 +30,7 @@ fn init_logger(debug: bool) {
.init();
}
#[cfg(target_os="linux")]
#[cfg(target_os = "linux")]
fn should_switch_to_poll(e: &Error) -> bool {
use nix::libc;
@ -40,9 +40,8 @@ fn should_switch_to_poll(e: &Error) -> bool {
}
}
#[cfg(not(target_os="linux"))]
#[cfg(not(target_os = "linux"))]
fn should_switch_to_poll(_: &Error) -> bool {
// not known conditions to switch
false
}