From 095829a37aa7d9c838d977fc1708d426ddb8c771 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Thu, 14 May 2020 11:37:51 +0800 Subject: [PATCH 01/18] Allow set_errno on Linux --- library/std/src/sys/unix/os.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/unix/os.rs b/library/std/src/sys/unix/os.rs index 2fcb5b9c4e66e..70753f785ec2e 100644 --- a/library/std/src/sys/unix/os.rs +++ b/library/std/src/sys/unix/os.rs @@ -70,7 +70,7 @@ pub fn errno() -> i32 { } /// Sets the platform-specific value of errno -#[cfg(all(not(target_os = "linux"), not(target_os = "dragonfly")))] // needed for readdir and syscall! +#[cfg(not(target_os = "dragonfly"))] // needed for readdir and syscall! #[allow(dead_code)] // but not all target cfgs actually end up using it pub fn set_errno(e: i32) { unsafe { *errno_location() = e as c_int } From 191e31998b021896291cf64e7ec9e245310cf52d Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Mon, 20 Jul 2020 18:43:23 +0800 Subject: [PATCH 02/18] [WIP] size limit for command line --- .../src/sys/unix/process/process_common.rs | 47 +++++- library/std/src/sys/windows/process.rs | 157 ++++++++++++------ 2 files changed, 148 insertions(+), 56 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 6e33cdd3c4826..93967ea5d1d23 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -13,7 +13,7 @@ use crate::sys_common::process::CommandEnv; #[cfg(not(target_os = "fuchsia"))] use crate::sys::fs::OpenOptions; -use libc::{c_char, c_int, gid_t, uid_t, EXIT_FAILURE, EXIT_SUCCESS}; +use libc::{c_char, c_int, gid_t, strlen, uid_t, EXIT_FAILURE, EXIT_SUCCESS}; cfg_if::cfg_if! { if #[cfg(target_os = "fuchsia")] { @@ -75,6 +75,7 @@ pub struct Command { args: Vec, argv: Argv, env: CommandEnv, + arg_max: Option, cwd: Option, uid: Option, @@ -137,6 +138,7 @@ impl Command { args: vec![program.clone()], program, env: Default::default(), + arg_max: Default::default(), cwd: None, uid: None, gid: None, @@ -202,6 +204,49 @@ impl Command { self.gid } + pub fn get_size(&mut self) -> io::Result { + use crate::mem; + let argv = self.argv.0; + let argv_size: usize = argv.iter().map(|x| unsafe { strlen(*x) + 1 }).sum::() + + (argv.len() + 1) * mem::size_of::<*const u8>(); + + // Envp size calculation is approximate. + let env = self.env.capture(); + let env_size: usize = env + .iter() + .map(|(k, v)| unsafe { + os2c(k.as_ref(), &mut self.saw_nul).to_bytes().len() + + os2c(v.as_ref(), &mut self.saw_nul).to_bytes().len() + + 2 + }) + .sum::() + + (env.len() + 1) * mem::size_of::<*const u8>(); + + Ok(argv_size + env_size) + } + + pub fn check_size(&mut self, refresh: bool) -> io::Result { + use crate::sys; + use core::convert::TryInto; + if refresh || self.arg_max.is_none() { + let (limit, errno) = unsafe { + let old_errno = sys::os::errno(); + sys::os::set_errno(0); + let limit = libc::sysconf(libc::_SC_ARG_MAX); + let errno = sys::os::errno(); + sys::os::set_errno(old_errno); + (limit, errno) + }; + + if errno != 0 { + return Err(io::Error::from_raw_os_error(errno)); + } else { + self.arg_max = limit.try_into().ok(); + } + } + Ok(self.arg_max.unwrap() < 0 || self.get_size()? < (self.arg_max.unwrap() as usize)) + } + pub fn get_closures(&mut self) -> &mut Vec io::Result<()> + Send + Sync>> { &mut self.closures } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 7d6d4775eec8a..2f4a2b455efb0 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -65,6 +65,8 @@ fn ensure_no_nuls>(str: T) -> io::Result { } } +// 32768 minus NUL plus starting space in our implementation +const CMDLINE_MAX: usize = 32768; pub struct Command { program: OsString, args: Vec, @@ -75,6 +77,8 @@ pub struct Command { stdin: Option, stdout: Option, stderr: Option, + cmdline: Vec, + cmdline_error: Option, } pub enum Stdio { @@ -106,11 +110,32 @@ impl Command { stdin: None, stdout: None, stderr: None, + cmdline: Vec::new(), + cmdline_error: None, } } + pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { + self.args.push(arg.to_os_string()); + self.cmdline.push(' ' as u16); + let result = append_arg(&mut cmd, arg, false); + if result.is_err() { + self.cmdline.truncate(self.cmdline.len() - 1); + result + } else if self.cmdline.size() >= CMDLINE_MAX { + // Roll back oversized + self.cmdline.truncate(self.cmdline.len() - 1 - result.unwrap()); + Err(io::Error::new(ErrorKind::InvalidInput, "oversized cmdline")) + } + Ok() + } pub fn arg(&mut self, arg: &OsStr) { - self.args.push(arg.to_os_string()) + if self.cmdline_error.is_none() { + let result = self.maybe_arg(self, arg); + if result.is_err() { + self.cmdline_error = Some(result.expect_err()); + } + } } pub fn env_mut(&mut self) -> &mut CommandEnv { &mut self.env @@ -136,34 +161,45 @@ impl Command { default: Stdio, needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { + if self.cmdline_error.is_some() { + return self.cmdline_error.unwrap(); + } + let maybe_env = self.env.capture_if_changed(); // To have the spawning semantics of unix/windows stay the same, we need // to read the *child's* PATH if one is provided. See #15149 for more // details. - let program = maybe_env.as_ref().and_then(|env| { - if let Some(v) = env.get(OsStr::new("PATH")) { - // Split the value and test each path to see if the - // program exists. - for path in split_paths(&v) { - let path = path - .join(self.program.to_str().unwrap()) - .with_extension(env::consts::EXE_EXTENSION); - if fs::metadata(&path).is_ok() { - return Some(path.into_os_string()); + let program = maybe_env + .as_ref() + .and_then(|env| { + if let Some(v) = env.get(OsStr::new("PATH")) { + // Split the value and test each path to see if the + // program exists. + for path in split_paths(&v) { + let path = path + .join(self.program.to_str().unwrap()) + .with_extension(env::consts::EXE_EXTENSION); + if fs::metadata(&path).is_ok() { + return Some(path.into_os_string()); + } } } - } - None - }); + None + }) + .as_ref() + .unwrap_or(&self.program); + + // Prepare and terminate the application name and the cmdline + // XXX: this won't work for 16-bit, might be preferable to do a extend_from_slice + let program_str: Vec = Vec::new(); + append_arg(&mut program_str, program, true)?; + program_str.push(0); + self.cmdline.push(0); let mut si = zeroed_startupinfo(); si.cb = mem::size_of::() as c::DWORD; si.dwFlags = c::STARTF_USESTDHANDLES; - let program = program.as_ref().unwrap_or(&self.program); - let mut cmd_str = make_command_line(program, &self.args)?; - cmd_str.push(0); // add null terminator - // stolen from the libuv code. let mut flags = self.flags | c::CREATE_UNICODE_ENVIRONMENT; if self.detach { @@ -201,8 +237,8 @@ impl Command { unsafe { cvt(c::CreateProcessW( - ptr::null(), - cmd_str.as_mut_ptr(), + program_str.as_mut_ptr(), + self.cmdline.as_mut_ptr().offset(1), // Skip the starting space ptr::null_mut(), ptr::null_mut(), c::TRUE, @@ -221,6 +257,14 @@ impl Command { Ok((Process { handle: Handle::new(pi.hProcess) }, pipes)) } + + pub fn get_size(&mut self) -> io::Result { + let (_, cmd_str) = self.prepare_command_line()?; + Ok(cmd_str.len()) + } + pub fn check_size(&mut self, _refresh: bool) -> io::Result { + Ok(self.get_size()? < 32767) + } } impl fmt::Debug for Command { @@ -445,6 +489,44 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION { } } +fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> io::Result { + let mut addsize: usize = 0; + // If an argument has 0 characters then we need to quote it to ensure + // that it actually gets passed through on the command line or otherwise + // it will be dropped entirely when parsed on the other end. + ensure_no_nuls(arg)?; + let arg_bytes = &arg.as_inner().inner.as_inner(); + let quote = + force_quotes || arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(); + if quote { + cmd.push('"' as u16); + addsize += 1; + } + + let mut backslashes: usize = 0; + for x in arg.encode_wide() { + if x == '\\' as u16 { + backslashes += 1; + } else { + if x == '"' as u16 { + // Add n+1 backslashes to total 2n+1 before internal '"'. + cmd.extend((0..=backslashes).map(|_| '\\' as u16)); + addsize += backslashes + 1; + } + backslashes = 0; + } + cmd.push(x); + } + + if quote { + // Add n backslashes to total 2n before ending '"'. + cmd.extend((0..backslashes).map(|_| '\\' as u16)); + cmd.push('"' as u16); + addsize += backslashes + 1; + } + Ok(addsize) +} + // Produces a wide string *without terminating null*; returns an error if // `prog` or any of the `args` contain a nul. fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result> { @@ -459,41 +541,6 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result> { append_arg(&mut cmd, arg, false)?; } return Ok(cmd); - - fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> io::Result<()> { - // If an argument has 0 characters then we need to quote it to ensure - // that it actually gets passed through on the command line or otherwise - // it will be dropped entirely when parsed on the other end. - ensure_no_nuls(arg)?; - let arg_bytes = &arg.as_inner().inner.as_inner(); - let quote = force_quotes - || arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') - || arg_bytes.is_empty(); - if quote { - cmd.push('"' as u16); - } - - let mut backslashes: usize = 0; - for x in arg.encode_wide() { - if x == '\\' as u16 { - backslashes += 1; - } else { - if x == '"' as u16 { - // Add n+1 backslashes to total 2n+1 before internal '"'. - cmd.extend((0..=backslashes).map(|_| '\\' as u16)); - } - backslashes = 0; - } - cmd.push(x); - } - - if quote { - // Add n backslashes to total 2n before ending '"'. - cmd.extend((0..backslashes).map(|_| '\\' as u16)); - cmd.push('"' as u16); - } - Ok(()) - } } fn make_envp(maybe_env: Option>) -> io::Result<(*mut c_void, Vec)> { From 0aa20e7980526a58e1f81aace364f2c877e52ea1 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Wed, 29 Jul 2020 10:15:53 +0800 Subject: [PATCH 03/18] fixup! CI complaint --- library/std/src/sys/unix/process/process_common.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 93967ea5d1d23..0eb879d476807 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -206,7 +206,7 @@ impl Command { pub fn get_size(&mut self) -> io::Result { use crate::mem; - let argv = self.argv.0; + let argv = &self.argv.0; let argv_size: usize = argv.iter().map(|x| unsafe { strlen(*x) + 1 }).sum::() + (argv.len() + 1) * mem::size_of::<*const u8>(); @@ -214,11 +214,11 @@ impl Command { let env = self.env.capture(); let env_size: usize = env .iter() - .map(|(k, v)| unsafe { + .map(|(k, v)| os2c(k.as_ref(), &mut self.saw_nul).to_bytes().len() + os2c(v.as_ref(), &mut self.saw_nul).to_bytes().len() + 2 - }) + ) .sum::() + (env.len() + 1) * mem::size_of::<*const u8>(); From 81dc1199ece682f0b69627e6382f368feef16862 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Wed, 29 Jul 2020 19:52:58 +0800 Subject: [PATCH 04/18] unix: try_add (QUADRATIC DONT USE) I still need to come up witth a place for the trait to go. Honestly I don't know where to put the file yet. This thing being quadratic is a problem too. I guess I will make a little struct with the current length and argc in it to keep track. (And yeah it only works as long as envp is unchanged.) --- .../src/sys/unix/process/process_common.rs | 66 ++++++++++++++----- .../src/sys/unix/process/process_fuchsia.rs | 11 +--- .../std/src/sys/unix/process/process_unix.rs | 26 +++++--- 3 files changed, 69 insertions(+), 34 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 0eb879d476807..b4031ec016eee 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -80,7 +80,7 @@ pub struct Command { cwd: Option, uid: Option, gid: Option, - saw_nul: bool, + problem: Problem, closures: Vec io::Result<()> + Send + Sync>>, stdin: Option, stdout: Option, @@ -129,10 +129,17 @@ pub enum Stdio { Fd(FileDesc), } +#[derive(Copy, Clone)] +pub enum Problem { + Ok, + SawNul, + Oversized, +} + impl Command { pub fn new(program: &OsStr) -> Command { - let mut saw_nul = false; - let program = os2c(program, &mut saw_nul); + let mut problem = Problem::Ok; + let program = os2c(program, &mut problem); Command { argv: Argv(vec![program.as_ptr(), ptr::null()]), args: vec![program.clone()], @@ -142,7 +149,7 @@ impl Command { cwd: None, uid: None, gid: None, - saw_nul, + problem, closures: Vec::new(), stdin: None, stdout: None, @@ -152,16 +159,25 @@ impl Command { pub fn set_arg_0(&mut self, arg: &OsStr) { // Set a new arg0 - let arg = os2c(arg, &mut self.saw_nul); + let arg = os2c(arg, &mut self.problem); debug_assert!(self.argv.0.len() > 1); self.argv.0[0] = arg.as_ptr(); self.args[0] = arg; } + pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { + self.arg(arg); + self.problem.as_result()?; + if self.check_size(false)? == false { + self.problem = Problem::Oversized; + } + self.problem.as_result() + } + pub fn arg(&mut self, arg: &OsStr) { // Overwrite the trailing NULL pointer in `argv` and then add a new null // pointer. - let arg = os2c(arg, &mut self.saw_nul); + let arg = os2c(arg, &mut self.problem); self.argv.0[self.args.len()] = arg.as_ptr(); self.argv.0.push(ptr::null()); @@ -171,7 +187,7 @@ impl Command { } pub fn cwd(&mut self, dir: &OsStr) { - self.cwd = Some(os2c(dir, &mut self.saw_nul)); + self.cwd = Some(os2c(dir, &mut self.problem)); } pub fn uid(&mut self, id: uid_t) { self.uid = Some(id); @@ -180,8 +196,8 @@ impl Command { self.gid = Some(id); } - pub fn saw_nul(&self) -> bool { - self.saw_nul + pub fn problem(&self) -> Problem { + self.problem } pub fn get_argv(&self) -> &Vec<*const c_char> { &self.argv.0 @@ -214,11 +230,11 @@ impl Command { let env = self.env.capture(); let env_size: usize = env .iter() - .map(|(k, v)| - os2c(k.as_ref(), &mut self.saw_nul).to_bytes().len() - + os2c(v.as_ref(), &mut self.saw_nul).to_bytes().len() + .map(|(k, v)| { + os2c(k.as_ref(), &mut self.problem).to_bytes().len() + + os2c(v.as_ref(), &mut self.problem).to_bytes().len() + 2 - ) + }) .sum::() + (env.len() + 1) * mem::size_of::<*const u8>(); @@ -273,7 +289,7 @@ impl Command { pub fn capture_env(&mut self) -> Option { let maybe_env = self.env.capture_if_changed(); - maybe_env.map(|env| construct_envp(env, &mut self.saw_nul)) + maybe_env.map(|env| construct_envp(env, &mut self.problem)) } #[allow(dead_code)] pub fn env_saw_path(&self) -> bool { @@ -299,9 +315,9 @@ impl Command { } } -fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString { +fn os2c(s: &OsStr, problem: &mut Problem) -> CString { CString::new(s.as_bytes()).unwrap_or_else(|_e| { - *saw_nul = true; + *problem = Problem::SawNul; CString::new("").unwrap() }) } @@ -332,7 +348,7 @@ impl CStringArray { } } -fn construct_envp(env: BTreeMap, saw_nul: &mut bool) -> CStringArray { +fn construct_envp(env: BTreeMap, problem: &mut Problem) -> CStringArray { let mut result = CStringArray::with_capacity(env.len()); for (mut k, v) in env { // Reserve additional space for '=' and null terminator @@ -344,7 +360,7 @@ fn construct_envp(env: BTreeMap, saw_nul: &mut bool) -> CStr if let Ok(item) = CString::new(k.into_vec()) { result.push(item); } else { - *saw_nul = true; + *problem = Problem::SawNul; } } @@ -418,6 +434,20 @@ impl ChildStdio { } } +impl Problem { + pub fn as_result(&self) -> io::Result<()> { + match *self { + Problem::Ok => Ok(()), + Problem::SawNul => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data")) + } + Problem::Oversized => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Oversized command")) + } + } + } +} + impl fmt::Debug for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.program != self.args[0] { diff --git a/library/std/src/sys/unix/process/process_fuchsia.rs b/library/std/src/sys/unix/process/process_fuchsia.rs index 6daf2885baed0..36692b5dc839a 100644 --- a/library/std/src/sys/unix/process/process_fuchsia.rs +++ b/library/std/src/sys/unix/process/process_fuchsia.rs @@ -21,12 +21,7 @@ impl Command { ) -> io::Result<(Process, StdioPipes)> { let envp = self.capture_env(); - if self.saw_nul() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "nul byte found in provided data", - )); - } + self.problem().as_result()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -36,8 +31,8 @@ impl Command { } pub fn exec(&mut self, default: Stdio) -> io::Error { - if self.saw_nul() { - return io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data"); + if let Err(err) = self.problem().as_result() { + return err; } match self.setup_io(default, true) { diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 0f349dfa30216..89817f3c814f6 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -22,9 +22,7 @@ impl Command { let envp = self.capture_env(); - if self.saw_nul() { - return Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data")); - } + self.problem().as_result()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -110,8 +108,8 @@ impl Command { pub fn exec(&mut self, default: Stdio) -> io::Error { let envp = self.capture_env(); - if self.saw_nul() { - return io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data"); + if let Err(err) = self.problem().as_result() { + return err; } match self.setup_io(default, true) { @@ -389,7 +387,11 @@ impl Command { self.get_argv().as_ptr() as *const _, envp as *const _, ); - if ret == 0 { Ok(Some(p)) } else { Err(io::Error::from_raw_os_error(ret)) } + if ret == 0 { + Ok(Some(p)) + } else { + Err(io::Error::from_raw_os_error(ret)) + } } } } @@ -467,11 +469,19 @@ impl ExitStatus { } pub fn code(&self) -> Option { - if self.exited() { Some(unsafe { libc::WEXITSTATUS(self.0) }) } else { None } + if self.exited() { + Some(unsafe { libc::WEXITSTATUS(self.0) }) + } else { + None + } } pub fn signal(&self) -> Option { - if !self.exited() { Some(unsafe { libc::WTERMSIG(self.0) }) } else { None } + if !self.exited() { + Some(unsafe { libc::WTERMSIG(self.0) }) + } else { + None + } } } From ff126ba4b912b5043fb53d2bc05a25281664ce9d Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Wed, 29 Jul 2020 19:55:10 +0800 Subject: [PATCH 05/18] windows/process: fix some obvious issues, create some more It looks like I really can't get away with the whole storing error thing as they don't have Copy. Maybe I will end up using the ugly problem enum thing like I did with Unix too. Grrr... --- library/std/src/sys/windows/process.rs | 74 ++++++++++++++------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 2f4a2b455efb0..047145af2c4a5 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -118,22 +118,27 @@ impl Command { pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { self.args.push(arg.to_os_string()); self.cmdline.push(' ' as u16); - let result = append_arg(&mut cmd, arg, false); - if result.is_err() { - self.cmdline.truncate(self.cmdline.len() - 1); - result - } else if self.cmdline.size() >= CMDLINE_MAX { - // Roll back oversized - self.cmdline.truncate(self.cmdline.len() - 1 - result.unwrap()); - Err(io::Error::new(ErrorKind::InvalidInput, "oversized cmdline")) - } - Ok() + let result = append_arg(&mut self.cmdline, arg, false); + match result { + Err(err) => { + self.cmdline.truncate(self.cmdline.len() - 1); + return Err(err); + } + Ok(length) => { + if self.cmdline.len() >= CMDLINE_MAX { + // Roll back oversized + self.cmdline.truncate(self.cmdline.len() - 1 - length); + return Err(io::Error::new(ErrorKind::InvalidInput, "Oversized cmdline")); + } + } + }; + Ok(()) } pub fn arg(&mut self, arg: &OsStr) { if self.cmdline_error.is_none() { - let result = self.maybe_arg(self, arg); - if result.is_err() { - self.cmdline_error = Some(result.expect_err()); + let result = self.maybe_arg(arg); + if let Err(err) = result { + self.cmdline_error = Some(err); } } } @@ -161,37 +166,34 @@ impl Command { default: Stdio, needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { - if self.cmdline_error.is_some() { - return self.cmdline_error.unwrap(); + if let Some(err) = &self.cmdline_error { + return Err(err); } let maybe_env = self.env.capture_if_changed(); // To have the spawning semantics of unix/windows stay the same, we need // to read the *child's* PATH if one is provided. See #15149 for more // details. - let program = maybe_env - .as_ref() - .and_then(|env| { - if let Some(v) = env.get(OsStr::new("PATH")) { - // Split the value and test each path to see if the - // program exists. - for path in split_paths(&v) { - let path = path - .join(self.program.to_str().unwrap()) - .with_extension(env::consts::EXE_EXTENSION); - if fs::metadata(&path).is_ok() { - return Some(path.into_os_string()); - } + let rprogram = maybe_env.as_ref().and_then(|env| { + if let Some(v) = env.get(OsStr::new("PATH")) { + // Split the value and test each path to see if the + // program exists. + for path in split_paths(&v) { + let path = path + .join(self.program.to_str().unwrap()) + .with_extension(env::consts::EXE_EXTENSION); + if fs::metadata(&path).is_ok() { + return Some(path.into_os_string()); } } - None - }) - .as_ref() - .unwrap_or(&self.program); + } + None + }); + let program = rprogram.as_ref().unwrap_or(&self.program); // Prepare and terminate the application name and the cmdline // XXX: this won't work for 16-bit, might be preferable to do a extend_from_slice - let program_str: Vec = Vec::new(); + let mut program_str: Vec = Vec::new(); append_arg(&mut program_str, program, true)?; program_str.push(0); self.cmdline.push(0); @@ -259,8 +261,10 @@ impl Command { } pub fn get_size(&mut self) -> io::Result { - let (_, cmd_str) = self.prepare_command_line()?; - Ok(cmd_str.len()) + match &self.cmdline_error { + Some(err) => Err(err), + None => Ok(self.cmdline.len()), + } } pub fn check_size(&mut self, _refresh: bool) -> io::Result { Ok(self.get_size()? < 32767) From dd5e4406bb035b42f1de8ba601a980da8c58cf56 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Wed, 29 Jul 2020 21:03:25 +0800 Subject: [PATCH 06/18] unix: incremental size, no strlen --- .../std/src/sys/unix/process/process_common.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index b4031ec016eee..9e58bf9fead7a 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -4,6 +4,7 @@ use crate::collections::BTreeMap; use crate::ffi::{CStr, CString, OsStr, OsString}; use crate::fmt; use crate::io; +use crate::mem; use crate::ptr; use crate::sys::fd::FileDesc; use crate::sys::fs::File; @@ -13,7 +14,7 @@ use crate::sys_common::process::CommandEnv; #[cfg(not(target_os = "fuchsia"))] use crate::sys::fs::OpenOptions; -use libc::{c_char, c_int, gid_t, strlen, uid_t, EXIT_FAILURE, EXIT_SUCCESS}; +use libc::{c_char, c_int, gid_t, uid_t, EXIT_FAILURE, EXIT_SUCCESS}; cfg_if::cfg_if! { if #[cfg(target_os = "fuchsia")] { @@ -76,6 +77,7 @@ pub struct Command { argv: Argv, env: CommandEnv, arg_max: Option, + arg_size: usize, cwd: Option, uid: Option, @@ -146,6 +148,7 @@ impl Command { program, env: Default::default(), arg_max: Default::default(), + arg_size: mem::size_of::<*const u8>(), cwd: None, uid: None, gid: None, @@ -161,6 +164,8 @@ impl Command { // Set a new arg0 let arg = os2c(arg, &mut self.problem); debug_assert!(self.argv.0.len() > 1); + self.arg_size -= self.args[0].to_bytes().len(); + self.arg_size += arg.to_bytes().len(); self.argv.0[0] = arg.as_ptr(); self.args[0] = arg; } @@ -178,6 +183,7 @@ impl Command { // Overwrite the trailing NULL pointer in `argv` and then add a new null // pointer. let arg = os2c(arg, &mut self.problem); + self.arg_size += arg.to_bytes().len() + 1 + mem::size_of::<*const u8>(); self.argv.0[self.args.len()] = arg.as_ptr(); self.argv.0.push(ptr::null()); @@ -221,11 +227,6 @@ impl Command { } pub fn get_size(&mut self) -> io::Result { - use crate::mem; - let argv = &self.argv.0; - let argv_size: usize = argv.iter().map(|x| unsafe { strlen(*x) + 1 }).sum::() - + (argv.len() + 1) * mem::size_of::<*const u8>(); - // Envp size calculation is approximate. let env = self.env.capture(); let env_size: usize = env @@ -238,7 +239,7 @@ impl Command { .sum::() + (env.len() + 1) * mem::size_of::<*const u8>(); - Ok(argv_size + env_size) + Ok(self.arg_size + env_size) } pub fn check_size(&mut self, refresh: bool) -> io::Result { From 312dcd74f0813aca644c767f79eebd3dd28486d3 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Wed, 29 Jul 2020 21:49:37 +0800 Subject: [PATCH 07/18] fixup! unix: argv[0] has a size too --- library/std/src/sys/unix/process/process_common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 9e58bf9fead7a..2e3f642bf3653 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -148,7 +148,7 @@ impl Command { program, env: Default::default(), arg_max: Default::default(), - arg_size: mem::size_of::<*const u8>(), + arg_size: mem::size_of::<*const u8>() + program.to_bytes().len() + 1, cwd: None, uid: None, gid: None, From d190d3ea82dce1542b94a99e3f063a40f83211fb Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 31 Jul 2020 18:03:11 +0800 Subject: [PATCH 08/18] windows: sort the error type out --- library/std/src/sys/windows/process.rs | 67 ++++++++++++++++++-------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 047145af2c4a5..65dbebc2bab4e 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -57,9 +57,9 @@ impl AsRef for EnvKey { } } -fn ensure_no_nuls>(str: T) -> io::Result { +fn ensure_no_nuls>(str: T) -> Result { if str.as_ref().encode_wide().any(|b| b == 0) { - Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data")) + Err(Problem::SawNul) } else { Ok(str) } @@ -78,7 +78,7 @@ pub struct Command { stdout: Option, stderr: Option, cmdline: Vec, - cmdline_error: Option, + problem: Option, } pub enum Stdio { @@ -98,6 +98,11 @@ struct DropGuard<'a> { lock: &'a Mutex, } +enum Problem { + SawNul, + Oversized, +} + impl Command { pub fn new(program: &OsStr) -> Command { Command { @@ -111,36 +116,39 @@ impl Command { stdout: None, stderr: None, cmdline: Vec::new(), - cmdline_error: None, + problem: None, } } pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { + self.arg(arg); + + match &self.problem { + Some(err) => Err(err.into()), + None => Ok(()), + } + } + pub fn arg(&mut self, arg: &OsStr) { + if self.problem.is_some() { + return; + } + self.args.push(arg.to_os_string()); self.cmdline.push(' ' as u16); let result = append_arg(&mut self.cmdline, arg, false); match result { Err(err) => { self.cmdline.truncate(self.cmdline.len() - 1); - return Err(err); + self.problem = Some(err); } Ok(length) => { if self.cmdline.len() >= CMDLINE_MAX { // Roll back oversized self.cmdline.truncate(self.cmdline.len() - 1 - length); - return Err(io::Error::new(ErrorKind::InvalidInput, "Oversized cmdline")); + self.problem = Some(Problem::Oversized) } } }; - Ok(()) - } - pub fn arg(&mut self, arg: &OsStr) { - if self.cmdline_error.is_none() { - let result = self.maybe_arg(arg); - if let Err(err) = result { - self.cmdline_error = Some(err); - } - } } pub fn env_mut(&mut self) -> &mut CommandEnv { &mut self.env @@ -166,8 +174,8 @@ impl Command { default: Stdio, needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { - if let Some(err) = &self.cmdline_error { - return Err(err); + if let Some(err) = &self.problem { + return Err(err.into()); } let maybe_env = self.env.capture_if_changed(); @@ -261,13 +269,13 @@ impl Command { } pub fn get_size(&mut self) -> io::Result { - match &self.cmdline_error { - Some(err) => Err(err), + match &self.problem { + Some(err) => Err(err.into()), None => Ok(self.cmdline.len()), } } pub fn check_size(&mut self, _refresh: bool) -> io::Result { - Ok(self.get_size()? < 32767) + Ok(self.get_size()? < CMDLINE_MAX) } } @@ -355,6 +363,23 @@ impl From for Stdio { } } +impl From<&Problem> for io::Error { + fn from(problem: &Problem) -> io::Error { + match *problem { + Problem::SawNul => { + io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data") + } + Problem::Oversized => io::Error::new(ErrorKind::InvalidInput, "Oversized command"), + } + } +} + +impl From for io::Error { + fn from(problem: Problem) -> io::Error { + (&problem).into() + } +} + //////////////////////////////////////////////////////////////////////////////// // Processes //////////////////////////////////////////////////////////////////////////////// @@ -493,7 +518,7 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION { } } -fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> io::Result { +fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> Result { let mut addsize: usize = 0; // If an argument has 0 characters then we need to quote it to ensure // that it actually gets passed through on the command line or otherwise From fea9cef94efeb288d027b54897b407ae2c7a9e7b Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 31 Jul 2020 22:12:02 +0800 Subject: [PATCH 09/18] unix, windows: add temporary dead_code --- .../src/sys/unix/process/process_common.rs | 9 ++++++--- library/std/src/sys/windows/process.rs | 20 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 2e3f642bf3653..e852d44b1af73 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -76,6 +76,7 @@ pub struct Command { args: Vec, argv: Argv, env: CommandEnv, + env_size: Option, arg_max: Option, arg_size: usize, @@ -142,13 +143,14 @@ impl Command { pub fn new(program: &OsStr) -> Command { let mut problem = Problem::Ok; let program = os2c(program, &mut problem); + let program_size = program.to_bytes_with_nul().len(); Command { argv: Argv(vec![program.as_ptr(), ptr::null()]), args: vec![program.clone()], program, env: Default::default(), arg_max: Default::default(), - arg_size: mem::size_of::<*const u8>() + program.to_bytes().len() + 1, + arg_size: 2 * mem::size_of::<*const u8>() + program_size, cwd: None, uid: None, gid: None, @@ -170,6 +172,7 @@ impl Command { self.args[0] = arg; } + #[allow(dead_code)] pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { self.arg(arg); self.problem.as_result()?; @@ -183,7 +186,7 @@ impl Command { // Overwrite the trailing NULL pointer in `argv` and then add a new null // pointer. let arg = os2c(arg, &mut self.problem); - self.arg_size += arg.to_bytes().len() + 1 + mem::size_of::<*const u8>(); + self.arg_size += arg.to_bytes_with_nul().len() + mem::size_of::<*const u8>(); self.argv.0[self.args.len()] = arg.as_ptr(); self.argv.0.push(ptr::null()); @@ -443,7 +446,7 @@ impl Problem { Err(io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data")) } Problem::Oversized => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Oversized command")) + Err(io::Error::new(io::ErrorKind::InvalidInput, "command exceeds maximum size")) } } } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 65dbebc2bab4e..687c66c97ffb7 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -120,6 +120,7 @@ impl Command { } } + #[allow(dead_code)] pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { self.arg(arg); @@ -268,12 +269,14 @@ impl Command { Ok((Process { handle: Handle::new(pi.hProcess) }, pipes)) } + #[allow(dead_code)] pub fn get_size(&mut self) -> io::Result { match &self.problem { Some(err) => Err(err.into()), None => Ok(self.cmdline.len()), } } + #[allow(dead_code)] pub fn check_size(&mut self, _refresh: bool) -> io::Result { Ok(self.get_size()? < CMDLINE_MAX) } @@ -363,19 +366,21 @@ impl From for Stdio { } } -impl From<&Problem> for io::Error { - fn from(problem: &Problem) -> io::Error { +impl From<&Problem> for Error { + fn from(problem: &Problem) -> Error { match *problem { Problem::SawNul => { - io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data") + Error::new(ErrorKind::InvalidInput, "nul byte found in provided data") + } + Problem::Oversized => { + Error::new(ErrorKind::InvalidInput, "command exceeds maximum size") } - Problem::Oversized => io::Error::new(ErrorKind::InvalidInput, "Oversized command"), } } } -impl From for io::Error { - fn from(problem: Problem) -> io::Error { +impl From for Error { + fn from(problem: Problem) -> Error { (&problem).into() } } @@ -422,7 +427,7 @@ impl Process { c::WAIT_TIMEOUT => { return Ok(None); } - _ => return Err(io::Error::last_os_error()), + _ => return Err(Error::last_os_error()), } let mut status = 0; cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?; @@ -558,6 +563,7 @@ fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> Result io::Result> { // Encode the command and arguments in a command line string such // that the spawned process may recover them using CommandLineToArgvW. From 29d21a84a01db7102aad989dcb73d9495a16338f Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 31 Jul 2020 22:12:16 +0800 Subject: [PATCH 10/18] unix: cache envp --- .../src/sys/unix/process/process_common.rs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index e852d44b1af73..cf8744ca50847 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -149,6 +149,7 @@ impl Command { args: vec![program.clone()], program, env: Default::default(), + env_size: None, arg_max: Default::default(), arg_size: 2 * mem::size_of::<*const u8>() + program_size, cwd: None, @@ -231,18 +232,22 @@ impl Command { pub fn get_size(&mut self) -> io::Result { // Envp size calculation is approximate. - let env = self.env.capture(); - let env_size: usize = env - .iter() - .map(|(k, v)| { - os2c(k.as_ref(), &mut self.problem).to_bytes().len() - + os2c(v.as_ref(), &mut self.problem).to_bytes().len() - + 2 - }) - .sum::() - + (env.len() + 1) * mem::size_of::<*const u8>(); - - Ok(self.arg_size + env_size) + let env = &self.env; + let problem = &mut self.problem; + let env_size = self.env_size.get_or_insert_with(|| { + let env_map = env.capture(); + env_map + .iter() + .map(|(k, v)| { + os2c(k.as_ref(), problem).to_bytes().len() + + os2c(v.as_ref(), problem).to_bytes().len() + + 2 + }) + .sum::() + + (env_map.len() + 1) * mem::size_of::<*const u8>() + }); + + Ok(self.arg_size + *env_size) } pub fn check_size(&mut self, refresh: bool) -> io::Result { @@ -288,6 +293,7 @@ impl Command { } pub fn env_mut(&mut self) -> &mut CommandEnv { + self.env_size = None; &mut self.env } From 82502b9e042ddd2b61a91cf6630b40eb722222c3 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sat, 1 Aug 2020 00:50:08 +0800 Subject: [PATCH 11/18] Windows: draft traits for raw args and custom escapers --- library/std/src/sys/windows/process.rs | 67 +++++++++++++++++++++----- src/stdarch | 1 + 2 files changed, 57 insertions(+), 11 deletions(-) create mode 160000 src/stdarch diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 687c66c97ffb7..ab917fe6e87bb 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -98,11 +98,13 @@ struct DropGuard<'a> { lock: &'a Mutex, } -enum Problem { +pub enum Problem { SawNul, Oversized, } +pub struct RawArg<'a>(&'a OsStr); + impl Command { pub fn new(program: &OsStr) -> Command { Command { @@ -136,7 +138,7 @@ impl Command { self.args.push(arg.to_os_string()); self.cmdline.push(' ' as u16); - let result = append_arg(&mut self.cmdline, arg, false); + let result = arg.append_to(&mut self.cmdline, false); match result { Err(err) => { self.cmdline.truncate(self.cmdline.len() - 1); @@ -203,7 +205,7 @@ impl Command { // Prepare and terminate the application name and the cmdline // XXX: this won't work for 16-bit, might be preferable to do a extend_from_slice let mut program_str: Vec = Vec::new(); - append_arg(&mut program_str, program, true)?; + program.as_os_str().append_to(&mut program_str, true)?; program_str.push(0); self.cmdline.push(0); @@ -385,6 +387,31 @@ impl From for Error { } } +pub trait Arg { + fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; + fn arg_len(&self, force_quotes: bool) -> Result; +} + +impl Arg for &OsStr { + fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result { + append_arg(&mut Some(cmd), &self, force_quotes) + } + fn arg_len(&self, force_quotes: bool) -> Result { + append_arg(&mut None, &self, force_quotes) + } +} + +#[allow(dead_code)] +impl Arg for RawArg<'_> { + fn append_to(&self, cmd: &mut Vec, _fq: bool) -> Result { + cmd.extend(self.0.encode_wide()); + self.arg_len(_fq) + } + fn arg_len(&self, _: bool) -> Result { + Ok(self.0.encode_wide().count()) + } +} + //////////////////////////////////////////////////////////////////////////////// // Processes //////////////////////////////////////////////////////////////////////////////// @@ -523,7 +550,23 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION { } } -fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> Result { +macro_rules! if_some { + ($e: expr, $id:ident, $b:block) => { + if let &mut Some(ref mut $id) = $e + $b + }; + ($e: expr, $id:ident, $s:stmt) => { + if_some!($e, $id, { $s }) + }; +} + +// This is effed up. Yeah, how the heck do I pass an optional, mutable reference around? +// @see https://users.rust-lang.org/t/idiomatic-way-for-passing-an-optional-mutable-reference-around/7947 +fn append_arg( + maybe_cmd: &mut Option<&mut Vec>, + arg: &OsStr, + force_quotes: bool, +) -> Result { let mut addsize: usize = 0; // If an argument has 0 characters then we need to quote it to ensure // that it actually gets passed through on the command line or otherwise @@ -533,7 +576,7 @@ fn append_arg(cmd: &mut Vec, arg: &OsStr, force_quotes: bool) -> Result, arg: &OsStr, force_quotes: bool) -> Result io::Result> { let mut cmd: Vec = Vec::new(); // Always quote the program name so CreateProcess doesn't interpret args as // part of the name if the binary wasn't found first time. - append_arg(&mut cmd, prog, true)?; + prog.append_to(&mut cmd, true)?; for arg in args { cmd.push(' ' as u16); - append_arg(&mut cmd, arg, false)?; + arg.as_os_str().append_to(&mut cmd, false)?; } return Ok(cmd); } diff --git a/src/stdarch b/src/stdarch new file mode 160000 index 0000000000000..d10eefc62284c --- /dev/null +++ b/src/stdarch @@ -0,0 +1 @@ +Subproject commit d10eefc62284c40c5a95a2eed19fc1f414a5364d From 0357037a61b279be1bb70438bf28a2b06c66248c Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sat, 1 Aug 2020 16:14:42 +0800 Subject: [PATCH 12/18] Windows: raw args trait --- library/std/src/sys/windows/ext/process.rs | 11 +++++++++ library/std/src/sys/windows/process.rs | 28 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/windows/ext/process.rs b/library/std/src/sys/windows/ext/process.rs index 8c34a9faf1d4a..d4b325c34b7cd 100644 --- a/library/std/src/sys/windows/ext/process.rs +++ b/library/std/src/sys/windows/ext/process.rs @@ -5,6 +5,8 @@ use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use crate::process; use crate::sys; +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +pub use crate::sys::process::{Arg, RawArg}; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; #[stable(feature = "process_extensions", since = "1.2.0")] @@ -102,6 +104,10 @@ pub trait CommandExt { /// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags #[stable(feature = "windows_process_extensions", since = "1.16.0")] fn creation_flags(&mut self, flags: u32) -> &mut process::Command; + + /// Pass an argument with custom escape rules. + #[unstable(feature = "windows_raw_cmdline", issue = "74549")] + fn arg_ext(&mut self, arg: impl Arg) -> &mut process::Command; } #[stable(feature = "windows_process_extensions", since = "1.16.0")] @@ -110,4 +116,9 @@ impl CommandExt for process::Command { self.as_inner_mut().creation_flags(flags); self } + + fn arg_ext(&mut self, arg: impl Arg) -> &mut process::Command { + self.as_inner_mut().arg_ext(arg); + self + } } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index ab917fe6e87bb..fc4f27e0714d9 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -103,6 +103,8 @@ pub enum Problem { Oversized, } +/// Argument type with no escaping. +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] pub struct RawArg<'a>(&'a OsStr); impl Command { @@ -123,15 +125,15 @@ impl Command { } #[allow(dead_code)] - pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { - self.arg(arg); + pub fn maybe_arg_ext(&mut self, arg: impl Arg) -> io::Result<()> { + self.arg_ext(arg); match &self.problem { Some(err) => Err(err.into()), None => Ok(()), } } - pub fn arg(&mut self, arg: &OsStr) { + pub fn arg_ext(&mut self, arg: impl Arg) { if self.problem.is_some() { return; } @@ -153,6 +155,13 @@ impl Command { } }; } + #[allow(dead_code)] + pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { + self.maybe_arg_ext(arg) + } + pub fn arg(&mut self, arg: &OsStr) { + self.arg_ext(arg) + } pub fn env_mut(&mut self) -> &mut CommandEnv { &mut self.env } @@ -203,7 +212,8 @@ impl Command { let program = rprogram.as_ref().unwrap_or(&self.program); // Prepare and terminate the application name and the cmdline - // XXX: this won't work for 16-bit, might be preferable to do a extend_from_slice + // FIXME: this won't work for 16-bit, which requires the program + // to be put on the cmdline. Do an extend_from_slice? let mut program_str: Vec = Vec::new(); program.as_os_str().append_to(&mut program_str, true)?; program_str.push(0); @@ -387,9 +397,13 @@ impl From for Error { } } +/// Types that can be appended to a Windows command-line. Used for custom escaping. +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] pub trait Arg { + /// fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; fn arg_len(&self, force_quotes: bool) -> Result; + fn to_os_string(&self) -> OsString; } impl Arg for &OsStr { @@ -399,6 +413,9 @@ impl Arg for &OsStr { fn arg_len(&self, force_quotes: bool) -> Result { append_arg(&mut None, &self, force_quotes) } + fn to_os_string(&self) -> OsString { + OsStr::to_os_string(&self) + } } #[allow(dead_code)] @@ -410,6 +427,9 @@ impl Arg for RawArg<'_> { fn arg_len(&self, _: bool) -> Result { Ok(self.0.encode_wide().count()) } + fn to_os_string(&self) -> OsString { + OsStr::to_os_string(&(self.0)) + } } //////////////////////////////////////////////////////////////////////////////// From c7e530279d4a1b25f9a0918d16c70a4be3eacf99 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sat, 1 Aug 2020 20:00:17 +0800 Subject: [PATCH 13/18] Windows: minor rename while I figure out where to put sized --- library/std/src/sys/windows/process.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index fc4f27e0714d9..a29927ef4ece4 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -103,6 +103,15 @@ pub enum Problem { Oversized, } +/// Types that can be appended to a Windows command-line. Used for custom escaping. +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +pub trait Arg { + fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; + #[unstable(feature = "command_sized", issue = "74549")] + fn arg_size(&self, force_quotes: bool) -> Result; + fn to_os_string(&self) -> OsString; +} + /// Argument type with no escaping. #[unstable(feature = "windows_raw_cmdline", issue = "74549")] pub struct RawArg<'a>(&'a OsStr); @@ -397,20 +406,11 @@ impl From for Error { } } -/// Types that can be appended to a Windows command-line. Used for custom escaping. -#[unstable(feature = "windows_raw_cmdline", issue = "74549")] -pub trait Arg { - /// - fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; - fn arg_len(&self, force_quotes: bool) -> Result; - fn to_os_string(&self) -> OsString; -} - impl Arg for &OsStr { fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result { append_arg(&mut Some(cmd), &self, force_quotes) } - fn arg_len(&self, force_quotes: bool) -> Result { + fn arg_size(&self, force_quotes: bool) -> Result { append_arg(&mut None, &self, force_quotes) } fn to_os_string(&self) -> OsString { @@ -422,9 +422,9 @@ impl Arg for &OsStr { impl Arg for RawArg<'_> { fn append_to(&self, cmd: &mut Vec, _fq: bool) -> Result { cmd.extend(self.0.encode_wide()); - self.arg_len(_fq) + self.arg_size(_fq) } - fn arg_len(&self, _: bool) -> Result { + fn arg_size(&self, _: bool) -> Result { Ok(self.0.encode_wide().count()) } fn to_os_string(&self) -> OsString { From a9c93ff9058eae3084a6386c27e15f089520ea78 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sat, 1 Aug 2020 20:01:10 +0800 Subject: [PATCH 14/18] unix: minor error stuff --- .../src/sys/unix/process/process_common.rs | 59 +++++++++++++------ .../src/sys/unix/process/process_fuchsia.rs | 6 +- .../std/src/sys/unix/process/process_unix.rs | 6 +- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index cf8744ca50847..1d36b63884b25 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -83,7 +83,7 @@ pub struct Command { cwd: Option, uid: Option, gid: Option, - problem: Problem, + problem: Result<(), Problem>, closures: Vec io::Result<()> + Send + Sync>>, stdin: Option, stdout: Option, @@ -133,15 +133,21 @@ pub enum Stdio { } #[derive(Copy, Clone)] +#[unstable(feature = "command_sized", issue = "74549")] pub enum Problem { - Ok, SawNul, Oversized, } +/// A terrible interface for expressing how much size an arg takes up. +#[unstable(feature = "command_sized", issue = "74549")] +pub trait Arg { + fn arg_size(&self, force_quotes: bool) -> Result; +} + impl Command { pub fn new(program: &OsStr) -> Command { - let mut problem = Problem::Ok; + let mut problem = Ok(()); let program = os2c(program, &mut problem); let program_size = program.to_bytes_with_nul().len(); Command { @@ -176,11 +182,14 @@ impl Command { #[allow(dead_code)] pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { self.arg(arg); - self.problem.as_result()?; + self.problem?; if self.check_size(false)? == false { - self.problem = Problem::Oversized; + self.problem = Err(Problem::Oversized); + } + match &self.problem { + Err(err) => Err(err.into()), + Ok(()) => Ok(()), } - self.problem.as_result() } pub fn arg(&mut self, arg: &OsStr) { @@ -206,7 +215,7 @@ impl Command { self.gid = Some(id); } - pub fn problem(&self) -> Problem { + pub fn problem(&self) -> Result<(), Problem> { self.problem } pub fn get_argv(&self) -> &Vec<*const c_char> { @@ -325,9 +334,9 @@ impl Command { } } -fn os2c(s: &OsStr, problem: &mut Problem) -> CString { +fn os2c(s: &OsStr, problem: &mut Result<(), Problem>) -> CString { CString::new(s.as_bytes()).unwrap_or_else(|_e| { - *problem = Problem::SawNul; + *problem = Err(Problem::SawNul); CString::new("").unwrap() }) } @@ -358,7 +367,7 @@ impl CStringArray { } } -fn construct_envp(env: BTreeMap, problem: &mut Problem) -> CStringArray { +fn construct_envp(env: BTreeMap, problem: &mut Result<(), Problem>) -> CStringArray { let mut result = CStringArray::with_capacity(env.len()); for (mut k, v) in env { // Reserve additional space for '=' and null terminator @@ -370,7 +379,7 @@ fn construct_envp(env: BTreeMap, problem: &mut Problem) -> C if let Ok(item) = CString::new(k.into_vec()) { result.push(item); } else { - *problem = Problem::SawNul; + *problem = Err(Problem::SawNul); } } @@ -444,20 +453,36 @@ impl ChildStdio { } } -impl Problem { - pub fn as_result(&self) -> io::Result<()> { - match *self { - Problem::Ok => Ok(()), +#[unstable(feature = "command_sized", issue = "74549")] +impl From<&Problem> for io::Error { + fn from(problem: &Problem) -> io::Error { + match *problem { Problem::SawNul => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data")) + io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data") } Problem::Oversized => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "command exceeds maximum size")) + io::Error::new(io::ErrorKind::InvalidInput, "command exceeds maximum size") } } } } +#[unstable(feature = "command_sized", issue = "74549")] +impl From for io::Error { + fn from(problem: Problem) -> io::Error { + (&problem).into() + } +} + +impl Arg for &OsStr { + fn arg_size(&self, _: bool) -> Result { + let mut nul_problem: Result<(), Problem> = Ok(()); + let cstr = os2c(self, &mut nul_problem); + nul_problem?; + Ok(cstr.to_bytes_with_nul().len() + mem::size_of::<*const u8>()) + } +} + impl fmt::Debug for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.program != self.args[0] { diff --git a/library/std/src/sys/unix/process/process_fuchsia.rs b/library/std/src/sys/unix/process/process_fuchsia.rs index 36692b5dc839a..e32c8f9ad45dd 100644 --- a/library/std/src/sys/unix/process/process_fuchsia.rs +++ b/library/std/src/sys/unix/process/process_fuchsia.rs @@ -21,7 +21,7 @@ impl Command { ) -> io::Result<(Process, StdioPipes)> { let envp = self.capture_env(); - self.problem().as_result()?; + self.problem()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -31,8 +31,8 @@ impl Command { } pub fn exec(&mut self, default: Stdio) -> io::Error { - if let Err(err) = self.problem().as_result() { - return err; + if let Err(err) = self.problem() { + return err.into(); } match self.setup_io(default, true) { diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 89817f3c814f6..17ceb07409935 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -22,7 +22,7 @@ impl Command { let envp = self.capture_env(); - self.problem().as_result()?; + self.problem()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -108,8 +108,8 @@ impl Command { pub fn exec(&mut self, default: Stdio) -> io::Error { let envp = self.capture_env(); - if let Err(err) = self.problem().as_result() { - return err; + if let Err(err) = self.problem() { + return err.into(); } match self.setup_io(default, true) { From 18ec8e3e9f1ca70fcd22f9785fa6f3ee04525593 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 14 Aug 2020 08:44:43 +0800 Subject: [PATCH 15/18] windows: very rough xargs --- library/std/src/sys/windows/ext/process.rs | 104 ++++++++++++++++++++- library/std/src/sys/windows/process.rs | 39 ++++++-- 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/windows/ext/process.rs b/library/std/src/sys/windows/ext/process.rs index d4b325c34b7cd..db64a6067f362 100644 --- a/library/std/src/sys/windows/ext/process.rs +++ b/library/std/src/sys/windows/ext/process.rs @@ -2,12 +2,15 @@ #![stable(feature = "process_extensions", since = "1.2.0")] +use crate::ffi::OsStr; +use crate::io; use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use crate::process; use crate::sys; #[unstable(feature = "windows_raw_cmdline", issue = "74549")] -pub use crate::sys::process::{Arg, RawArg}; +pub use crate::sys::process::{Arg, Problem, RawArg}; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; +use core::convert::TryFrom; #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { @@ -108,6 +111,10 @@ pub trait CommandExt { /// Pass an argument with custom escape rules. #[unstable(feature = "windows_raw_cmdline", issue = "74549")] fn arg_ext(&mut self, arg: impl Arg) -> &mut process::Command; + + /// Pass arguments with custom escape rules. + #[unstable(feature = "windows_raw_cmdline", issue = "74549")] + fn args_ext(&mut self, args: impl IntoIterator) -> &mut process::Command; } #[stable(feature = "windows_process_extensions", since = "1.16.0")] @@ -121,4 +128,99 @@ impl CommandExt for process::Command { self.as_inner_mut().arg_ext(arg); self } + + fn args_ext(&mut self, args: impl IntoIterator) -> &mut process::Command { + for arg in args { + self.arg_ext(arg); + } + self + } +} + +/// Traits for handling a sized command. +// FIXME: This really should be somewhere else, since it will be duplicated for unix. sys_common? I have no idea. +// The implementations should apply to unix, but describing it to the type system is another thing. +#[unstable(feature = "command_sized", issue = "74549")] +pub trait CommandSized: core::marker::Sized { + /// Possibly pass an argument. + fn maybe_arg(&mut self, arg: impl Arg) -> io::Result<&mut Self>; + /// Possibly pass many arguments. + // FIXME: I don't know how to return the half-eaten iterator! + fn maybe_args( + &mut self, + args: impl IntoIterator, + ) -> Result<&mut Self, (usize, io::Error)>; + /// Build many commands. + fn xargs(program: S, args: I, before: Vec, after: Vec) -> io::Result> + where + I: IntoIterator, + S: AsRef + Copy, + A: Arg; +} + +#[unstable(feature = "command_sized", issue = "74549")] +impl CommandSized for process::Command { + fn maybe_arg(&mut self, arg: impl Arg) -> io::Result<&mut Self> { + self.as_inner_mut().maybe_arg_ext(arg)?; + Ok(self) + } + fn maybe_args( + &mut self, + args: impl IntoIterator, + ) -> Result<&mut Self, (usize, io::Error)> { + let mut count: usize = 0; + for arg in args { + if let Err(err) = self.as_inner_mut().maybe_arg_ext(arg) { + return Err((count, err)); + } + count += 1; + } + Ok(self) + } + fn xargs(program: S, args: I, before: Vec, after: Vec) -> io::Result> + where + I: IntoIterator, + S: AsRef + Copy, + A: Arg, + { + let mut ret = Vec::new(); + let mut cmd = Self::new(program); + let mut fresh: bool = true; + + // This performs a nul check. + let tail_size: usize = after + .iter() + .map(|x| Arg::arg_size(x, false)) + .collect::, Problem>>()? + .iter() + .sum(); + + if let Err(_) = isize::try_from(tail_size) { + return Err(Problem::Oversized.into()); + } + + cmd.args_ext(&before); + if cmd.as_inner_mut().available_size(false)? < (tail_size as isize) { + return Err(Problem::Oversized.into()); + } + + for arg in args { + let size = arg.arg_size(false)?; + // Negative case is catched outside of loop. + if (cmd.as_inner_mut().available_size(false)? as usize) < (size + tail_size) { + if fresh { + return Err(Problem::Oversized.into()); + } + cmd.args_ext(&after); + ret.push(cmd); + cmd = Self::new(program); + cmd.args_ext(&before); + } + cmd.maybe_arg(arg)?; + fresh = false; + } + cmd.args_ext(&after); + ret.push(cmd); + Ok(ret) + } } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index a29927ef4ece4..d63729a9bcc21 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -21,6 +21,7 @@ use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; use crate::sys_common::process::CommandEnv; use crate::sys_common::AsInner; +use core::convert::TryInto; use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS}; @@ -104,6 +105,7 @@ pub enum Problem { } /// Types that can be appended to a Windows command-line. Used for custom escaping. +// FIXME: the force-quoted one should probably be its own type. #[unstable(feature = "windows_raw_cmdline", issue = "74549")] pub trait Arg { fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; @@ -189,6 +191,13 @@ impl Command { pub fn creation_flags(&mut self, flags: u32) { self.flags = flags; } + #[allow(dead_code)] + pub fn problem(&self) -> io::Result<()> { + if let Some(err) = &self.problem { + return Err(err.into()); + } + Ok(()) + } pub fn spawn( &mut self, @@ -290,16 +299,19 @@ impl Command { Ok((Process { handle: Handle::new(pi.hProcess) }, pipes)) } - #[allow(dead_code)] pub fn get_size(&mut self) -> io::Result { match &self.problem { Some(err) => Err(err.into()), None => Ok(self.cmdline.len()), } } - #[allow(dead_code)] - pub fn check_size(&mut self, _refresh: bool) -> io::Result { - Ok(self.get_size()? < CMDLINE_MAX) + pub fn available_size(&mut self, _refresh: bool) -> io::Result { + let size: isize = match self.get_size()?.try_into() { + Ok(s) => Ok(s), + Err(_) => Err(io::Error::from(Problem::Oversized)), + }?; + + Ok((CMDLINE_MAX as isize) - size) } } @@ -411,7 +423,7 @@ impl Arg for &OsStr { append_arg(&mut Some(cmd), &self, force_quotes) } fn arg_size(&self, force_quotes: bool) -> Result { - append_arg(&mut None, &self, force_quotes) + Ok(append_arg(&mut None, &self, force_quotes)? + 1) } fn to_os_string(&self) -> OsString { OsStr::to_os_string(&self) @@ -425,13 +437,28 @@ impl Arg for RawArg<'_> { self.arg_size(_fq) } fn arg_size(&self, _: bool) -> Result { - Ok(self.0.encode_wide().count()) + Ok(self.0.encode_wide().count() + 1) } fn to_os_string(&self) -> OsString { OsStr::to_os_string(&(self.0)) } } +impl<'a, T> Arg for &'a T +where + T: Arg, +{ + fn append_to(&self, cmd: &mut Vec, _fq: bool) -> Result { + (*self).append_to(cmd, _fq) + } + fn arg_size(&self, _fq: bool) -> Result { + (*self).arg_size(_fq) + } + fn to_os_string(&self) -> OsString { + (*self).to_os_string() + } +} + //////////////////////////////////////////////////////////////////////////////// // Processes //////////////////////////////////////////////////////////////////////////////// From cb30a5ce123459576d09e8a7e9a8232c8fac8430 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 14 Aug 2020 10:58:34 +0800 Subject: [PATCH 16/18] minor: run formatter it wants tight lines! --- .../std/src/sys/unix/process/process_common.rs | 5 ++++- .../std/src/sys/unix/process/process_unix.rs | 18 +++--------------- library/std/src/sys/windows/process.rs | 6 +----- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 1d36b63884b25..f04a796f3fe94 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -367,7 +367,10 @@ impl CStringArray { } } -fn construct_envp(env: BTreeMap, problem: &mut Result<(), Problem>) -> CStringArray { +fn construct_envp( + env: BTreeMap, + problem: &mut Result<(), Problem>, +) -> CStringArray { let mut result = CStringArray::with_capacity(env.len()); for (mut k, v) in env { // Reserve additional space for '=' and null terminator diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 17ceb07409935..a2bcd7bfd5c78 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -387,11 +387,7 @@ impl Command { self.get_argv().as_ptr() as *const _, envp as *const _, ); - if ret == 0 { - Ok(Some(p)) - } else { - Err(io::Error::from_raw_os_error(ret)) - } + if ret == 0 { Ok(Some(p)) } else { Err(io::Error::from_raw_os_error(ret)) } } } } @@ -469,19 +465,11 @@ impl ExitStatus { } pub fn code(&self) -> Option { - if self.exited() { - Some(unsafe { libc::WEXITSTATUS(self.0) }) - } else { - None - } + if self.exited() { Some(unsafe { libc::WEXITSTATUS(self.0) }) } else { None } } pub fn signal(&self) -> Option { - if !self.exited() { - Some(unsafe { libc::WTERMSIG(self.0) }) - } else { - None - } + if !self.exited() { Some(unsafe { libc::WTERMSIG(self.0) }) } else { None } } } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index d63729a9bcc21..00a54c2ca5cee 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -59,11 +59,7 @@ impl AsRef for EnvKey { } fn ensure_no_nuls>(str: T) -> Result { - if str.as_ref().encode_wide().any(|b| b == 0) { - Err(Problem::SawNul) - } else { - Ok(str) - } + if str.as_ref().encode_wide().any(|b| b == 0) { Err(Problem::SawNul) } else { Ok(str) } } // 32768 minus NUL plus starting space in our implementation From d11f795487b2663751fb6db00edd01b142a9b6a4 Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Fri, 14 Aug 2020 11:13:39 +0800 Subject: [PATCH 17/18] fixup! remove dup stdarch in 82502b9 --- src/stdarch | 1 - 1 file changed, 1 deletion(-) delete mode 160000 src/stdarch diff --git a/src/stdarch b/src/stdarch deleted file mode 160000 index d10eefc62284c..0000000000000 --- a/src/stdarch +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d10eefc62284c40c5a95a2eed19fc1f414a5364d From c945ef4c97ab56977a3e34605e7c369b1ca4148f Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sun, 15 Nov 2020 09:54:40 +0800 Subject: [PATCH 18/18] Windows commandext: apply review suggestions --- library/std/src/sys/windows/ext/process.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/library/std/src/sys/windows/ext/process.rs b/library/std/src/sys/windows/ext/process.rs index 3882ae18dd61f..cb16b6e828b69 100644 --- a/library/std/src/sys/windows/ext/process.rs +++ b/library/std/src/sys/windows/ext/process.rs @@ -139,17 +139,19 @@ impl CommandExt for process::Command { #[unstable(feature = "command_sized", issue = "74549")] pub trait CommandSized: core::marker::Sized { /// Possibly pass an argument. + /// Returns an error if the size of the arguments would overflow the command line. The error contains the reason the remaining arguments could not be added. fn maybe_arg(&mut self, arg: impl Arg) -> io::Result<&mut Self>; - /// Possibly pass many arguments. - // FIXME: I don't know how to return the half-eaten iterator! + /// Possibly pass many arguments. + /// Returns an error if the size of the arguments would overflow the command line. The error contains the number of arguments added as well as the reason the remaining arguments could not be added. fn maybe_args( &mut self, - args: impl IntoIterator, + args: &mut impl Iterator, ) -> Result<&mut Self, (usize, io::Error)>; - /// Build many commands. - fn xargs(program: S, args: I, before: Vec, after: Vec) -> io::Result> + /// Build multiple commands to consume all arguments. + /// Returns an error if the size of an argument would overflow the command line. The error contains the reason the remaining arguments could not be added. + fn xargs(program: S, args: &mut I, before: Vec, after: Vec) -> io::Result> where - I: IntoIterator, + I: Iterator, S: AsRef + Copy, A: Arg; } @@ -162,7 +164,7 @@ impl CommandSized for process::Command { } fn maybe_args( &mut self, - args: impl IntoIterator, + args: &mut impl Iterator, ) -> Result<&mut Self, (usize, io::Error)> { let mut count: usize = 0; for arg in args { @@ -173,9 +175,9 @@ impl CommandSized for process::Command { } Ok(self) } - fn xargs(program: S, args: I, before: Vec, after: Vec) -> io::Result> + fn xargs(program: S, args: &mut I, before: Vec, after: Vec) -> io::Result> where - I: IntoIterator, + I: Iterator, S: AsRef + Copy, A: Arg, {