diff --git a/library/std/src/sys/unix/os.rs b/library/std/src/sys/unix/os.rs index d5e14bec76572..5cd21491ca329 100644 --- a/library/std/src/sys/unix/os.rs +++ b/library/std/src/sys/unix/os.rs @@ -73,7 +73,7 @@ pub fn errno() -> i32 { } /// Sets the platform-specific value of errno -#[cfg(all(not(target_os = "linux"), not(target_os = "dragonfly"), not(target_os = "vxworks")))] // needed for readdir and syscall! +#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks")))] // 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 } diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index 4b29b7020eb85..c5c91704b6b84 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -7,6 +7,7 @@ use crate::collections::BTreeMap; use crate::ffi::{CStr, CString, OsStr, OsString}; use crate::fmt; use crate::io; +use crate::mem; use crate::path::Path; use crate::ptr; use crate::sys::fd::FileDesc; @@ -81,11 +82,14 @@ pub struct Command { args: Vec, argv: Argv, env: CommandEnv, + env_size: Option, + arg_max: Option, + arg_size: usize, cwd: Option, uid: Option, gid: Option, - saw_nul: bool, + problem: Result<(), Problem>, closures: Vec io::Result<()> + Send + Sync>>, stdin: Option, stdout: Option, @@ -134,19 +138,36 @@ pub enum Stdio { Fd(FileDesc), } +#[derive(Copy, Clone)] +#[unstable(feature = "command_sized", issue = "74549")] +pub enum Problem { + 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 saw_nul = false; - let program = os2c(program, &mut saw_nul); + let mut 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(), + env_size: None, + arg_max: Default::default(), + arg_size: 2 * mem::size_of::<*const u8>() + program_size, cwd: None, uid: None, gid: None, - saw_nul, + problem, closures: Vec::new(), stdin: None, stdout: None, @@ -156,16 +177,32 @@ 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.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; } + #[allow(dead_code)] + pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> { + self.arg(arg); + self.problem?; + if self.check_size(false)? == false { + self.problem = Err(Problem::Oversized); + } + match &self.problem { + Err(err) => Err(err.into()), + Ok(()) => Ok(()), + } + } + 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.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()); @@ -175,7 +212,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); @@ -184,8 +221,8 @@ impl Command { self.gid = Some(id); } - pub fn saw_nul(&self) -> bool { - self.saw_nul + pub fn problem(&self) -> Result<(), Problem> { + self.problem } pub fn get_program(&self) -> &OsStr { @@ -227,6 +264,48 @@ impl Command { self.gid } + pub fn get_size(&mut self) -> io::Result { + // Envp size calculation is approximate. + 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 { + 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 } @@ -248,12 +327,13 @@ impl Command { } pub fn env_mut(&mut self) -> &mut CommandEnv { + self.env_size = None; &mut self.env } 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 { @@ -279,9 +359,9 @@ impl Command { } } -fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString { +fn os2c(s: &OsStr, problem: &mut Result<(), Problem>) -> CString { CString::new(s.as_bytes()).unwrap_or_else(|_e| { - *saw_nul = true; + *problem = Err(Problem::SawNul); CString::new("").unwrap() }) } @@ -312,7 +392,10 @@ impl CStringArray { } } -fn construct_envp(env: BTreeMap, saw_nul: &mut bool) -> 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 @@ -324,7 +407,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 = Err(Problem::SawNul); } } @@ -398,6 +481,36 @@ impl ChildStdio { } } +#[unstable(feature = "command_sized", issue = "74549")] +impl From<&Problem> for io::Error { + fn from(problem: &Problem) -> io::Error { + match *problem { + Problem::SawNul => { + io::Error::new(io::ErrorKind::InvalidInput, "nul byte found in provided data") + } + Problem::Oversized => { + 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 b64636c3f3d15..5b29417f238c5 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()?; 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() { + 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 b5be92e3eae00..7315d20b86e68 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -26,9 +26,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()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -114,8 +112,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() { + return err.into(); } match self.setup_io(default, true) { diff --git a/library/std/src/sys/windows/ext/process.rs b/library/std/src/sys/windows/ext/process.rs index 61e4c6a1d1718..cb16b6e828b69 100644 --- a/library/std/src/sys/windows/ext/process.rs +++ b/library/std/src/sys/windows/ext/process.rs @@ -2,10 +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, 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 { @@ -98,6 +103,14 @@ 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; + + /// 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")] @@ -106,4 +119,106 @@ 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 + } + + 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. + /// 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. + /// 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: &mut impl Iterator, + ) -> Result<&mut Self, (usize, io::Error)>; + /// 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: Iterator, + 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: &mut impl Iterator, + ) -> 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: &mut I, before: Vec, after: Vec) -> io::Result> + where + I: Iterator, + 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 243065b94b125..1c166f67d13c2 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -24,6 +24,7 @@ use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; use crate::sys_common::process::{CommandEnv, CommandEnvs}; use crate::sys_common::AsInner; +use core::convert::TryInto; use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS}; @@ -60,14 +61,12 @@ impl AsRef for EnvKey { } } -fn ensure_no_nuls>(str: T) -> io::Result { - if str.as_ref().encode_wide().any(|b| b == 0) { - Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data")) - } else { - Ok(str) - } +fn ensure_no_nuls>(str: T) -> Result { + 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 +const CMDLINE_MAX: usize = 32768; pub struct Command { program: OsString, args: Vec, @@ -78,6 +77,8 @@ pub struct Command { stdin: Option, stdout: Option, stderr: Option, + cmdline: Vec, + problem: Option, } pub enum Stdio { @@ -97,6 +98,25 @@ struct DropGuard<'a> { lock: &'a Mutex, } +pub enum Problem { + SawNul, + Oversized, +} + +/// 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; + #[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); + impl Command { pub fn new(program: &OsStr) -> Command { Command { @@ -109,11 +129,48 @@ impl Command { stdin: None, stdout: None, stderr: None, + cmdline: Vec::new(), + problem: None, + } + } + + #[allow(dead_code)] + 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_ext(&mut self, arg: impl Arg) { + if self.problem.is_some() { + return; + } + self.args.push(arg.to_os_string()); + self.cmdline.push(' ' as u16); + let result = arg.append_to(&mut self.cmdline, false); + match result { + Err(err) => { + self.cmdline.truncate(self.cmdline.len() - 1); + self.problem = Some(err); + } + Ok(length) => { + if self.cmdline.len() >= CMDLINE_MAX { + // Roll back oversized + self.cmdline.truncate(self.cmdline.len() - 1 - length); + self.problem = Some(Problem::Oversized) + } + } + }; + } + #[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.args.push(arg.to_os_string()) + self.arg_ext(arg) } pub fn env_mut(&mut self) -> &mut CommandEnv { &mut self.env @@ -133,6 +190,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 get_program(&self) -> &OsStr { &self.program @@ -156,11 +220,15 @@ impl Command { default: Stdio, needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { + if let Some(err) = &self.problem { + return Err(err.into()); + } + 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| { + 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. @@ -175,15 +243,20 @@ impl Command { } None }); + let program = rprogram.as_ref().unwrap_or(&self.program); + + // Prepare and terminate the application name and the cmdline + // 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); + 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 { @@ -221,8 +294,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, @@ -241,6 +314,21 @@ impl Command { Ok((Process { handle: Handle::new(pi.hProcess) }, pipes)) } + + pub fn get_size(&mut self) -> io::Result { + match &self.problem { + Some(err) => Err(err.into()), + None => Ok(self.cmdline.len()), + } + } + 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) + } } impl fmt::Debug for Command { @@ -327,6 +415,66 @@ impl From for Stdio { } } +impl From<&Problem> for Error { + fn from(problem: &Problem) -> Error { + match *problem { + Problem::SawNul => { + Error::new(ErrorKind::InvalidInput, "nul byte found in provided data") + } + Problem::Oversized => { + Error::new(ErrorKind::InvalidInput, "command exceeds maximum size") + } + } + } +} + +impl From for Error { + fn from(problem: Problem) -> Error { + (&problem).into() + } +} + +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_size(&self, force_quotes: bool) -> Result { + Ok(append_arg(&mut None, &self, force_quotes)? + 1) + } + fn to_os_string(&self) -> OsString { + OsStr::to_os_string(&self) + } +} + +#[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_size(_fq) + } + fn arg_size(&self, _: bool) -> Result { + 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 //////////////////////////////////////////////////////////////////////////////// @@ -369,7 +517,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))?; @@ -465,55 +613,77 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION { } } +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 + // 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 { + if_some!(maybe_cmd, cmd, 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 '"'. + if_some!(maybe_cmd, cmd, cmd.extend((0..=backslashes).map(|_| '\\' as u16))); + addsize += backslashes + 1; + } + backslashes = 0; + } + if_some!(maybe_cmd, cmd, cmd.push(x)); + } + + if quote { + // Add n backslashes to total 2n before ending '"'. + if_some!(maybe_cmd, cmd, { + 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. +#[allow(dead_code)] fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result> { // Encode the command and arguments in a command line string such // that the spawned process may recover them using CommandLineToArgvW. 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); - - 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)> {