diff --git a/Cargo.lock b/Cargo.lock index 925898f6bbeeb..364c77119bdbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,14 @@ dependencies = [ "cargo-util", "clap", "crates-io", +<<<<<<< HEAD "crossbeam-utils 0.8.3", +||||||| parent of 1725fb7e1bd (actually get size adjustment to work) + "crossbeam-utils 0.8.0", +======= + "crossbeam-utils 0.8.0", + "crypto-hash", +>>>>>>> 1725fb7e1bd (actually get size adjustment to work) "curl", "curl-sys", "env_logger 0.8.1", @@ -5569,11 +5576,12 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "vergen" -version = "5.1.0" +version = "5.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc87f9a7a9d61b15d51d1d3547284f67b6b4f1494ce3fc5814c101f35a5183" +checksum = "adf0b57f76a4f7e9673db1e7ffa4541d215ae8336fee45f5c1378bdeb22a0314" dependencies = [ "anyhow", + "cfg-if 1.0.0", "chrono", "enum-iterator", "getset", diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 355855bcd10e2..443f5af908c5a 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -1,5 +1,4 @@ //! Unix-specific extensions to primitives in the `std::process` module. - #![stable(feature = "rust1", since = "1.0.0")] use crate::ffi::OsStr; @@ -10,6 +9,9 @@ use crate::sealed::Sealed; use crate::sys; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; +use crate::sys_common::process_ext; +impl_command_sized! { prelude } + /// Unix-specific extensions to the [`process::Command`] builder. /// /// This trait is sealed: it cannot be implemented outside the standard library. @@ -335,3 +337,25 @@ impl IntoRawFd for process::ChildStderr { pub fn parent_id() -> u32 { crate::sys::os::getppid() } + +fn maybe_arg_ext(celf: &mut sys::process::Command, arg: impl Arg) -> io::Result<()> { + celf.maybe_arg(arg.to_plain()) +} + +fn args_ext( + celf: &mut process::Command, + args: impl IntoIterator, +) -> &mut process::Command { + for arg in args { + celf.arg(arg.to_plain()); + } + celf +} + +#[unstable(feature = "command_sized", issue = "74549")] +#[cfg(unix)] // doc hack +impl process_ext::CommandSized for process::Command { + impl_command_sized! { marg maybe_arg_ext } + impl_command_sized! { margs maybe_arg_ext } + impl_command_sized! { xargs args_ext } +} diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index 67756b15531bf..40f4bd7d8a22e 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -2,11 +2,38 @@ #![stable(feature = "process_extensions", since = "1.2.0")] +use crate::ffi::{OsStr, OsString}; +use crate::io; use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use crate::process; use crate::sealed::Sealed; use crate::sys; -use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; +use crate::os::windows::ffi::OsStrExt; +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +pub use crate::sys_common::process_ext::{Arg, Problem}; +use crate::sys_common::{process_ext, AsInner, AsInnerMut, FromInner, IntoInner}; +use core::convert::TryFrom; + +/// Argument type with no escaping. +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +pub struct RawArg<'a>(&'a OsStr); + +// FIXME: Inhibiting doc on non-Windows due to mismatching trait methods. +#[cfg(any(windows))] +#[doc(cfg(windows))] +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +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)) + } +} #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { @@ -125,6 +152,13 @@ pub trait CommandExt: Sealed { /// [2]: #[unstable(feature = "windows_process_extensions_force_quotes", issue = "82227")] fn force_quotes(&mut self, enabled: bool) -> &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")] @@ -138,4 +172,25 @@ impl CommandExt for process::Command { self.as_inner_mut().force_quotes(enabled); 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 + } +} + +// FIXME: export maybe_arg_ext so the macro doesn't explicitly reach for as_inner_mut() +#[unstable(feature = "command_sized", issue = "74549")] +#[cfg(windows)] // doc hack +impl process_ext::CommandSized for process::Command { + impl_command_sized! { marg sys::process::Command::maybe_arg_ext } + impl_command_sized! { margs sys::process::Command::maybe_arg_ext } + impl_command_sized! { xargs process::Command::args_ext } } diff --git a/library/std/src/sys/unix/os.rs b/library/std/src/sys/unix/os.rs index 51c3e5d175cca..8814a5742f736 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(not(any(target_os = "dragonfly", 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/mod.rs b/library/std/src/sys/unix/process/mod.rs index f67c70c01772f..ad0c0b5fce005 100644 --- a/library/std/src/sys/unix/process/mod.rs +++ b/library/std/src/sys/unix/process/mod.rs @@ -1,4 +1,4 @@ -pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes}; +pub use self::process_common::{Arg, Command, CommandArgs, ExitCode, Problem, Stdio, StdioPipes}; pub use self::process_inner::{ExitStatus, Process}; pub use crate::ffi::OsString as EnvKey; pub use crate::sys_common::process::CommandEnvs; diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index c5bdd1bda4a7a..c405463597fa2 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -7,12 +7,14 @@ 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; use crate::sys::fs::File; use crate::sys::pipe::{self, AnonPipe}; use crate::sys_common::process::{CommandEnv, CommandEnvs}; +pub use crate::sys_common::process_ext::{Arg, Problem}; #[cfg(not(target_os = "fuchsia"))] use crate::sys::fs::OpenOptions; @@ -69,11 +71,14 @@ pub struct Command { /// `args` to properly update this as well. 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>>, groups: Option>, stdin: Option, @@ -125,17 +130,21 @@ pub enum Stdio { 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(), groups: None, stdin: None, @@ -146,16 +155,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()); @@ -165,7 +190,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); @@ -177,8 +202,8 @@ impl Command { self.groups = Some(Box::from(groups)); } - pub fn saw_nul(&self) -> bool { - self.saw_nul + pub fn problem(&self) -> Result<(), Problem> { + self.problem } pub fn get_program(&self) -> &OsStr { @@ -224,6 +249,62 @@ impl Command { self.groups.as_deref() } + 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 { + Ok(self.available_size(refresh)? > 0) + } + + pub fn available_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 { + // FIXME: don't panic + let amax: isize = limit.try_into().unwrap(); + let psize: isize = sys::os::page_size() as isize; + // POSIX says the headroom should be 2048. + // Most implementations do 4096. Let's just do a whole page. + debug_assert!(amax < 0 || amax > psize); + self.arg_max = Some(amax - psize); + } + } + if self.arg_max.unwrap() < 0 { + Ok(isize::MAX) // HACK: more like "infinity", but not much different + } else { + Ok(self.arg_max.unwrap() - self.get_size()? as isize) + } + } + pub fn get_closures(&mut self) -> &mut Vec io::Result<()> + Send + Sync>> { &mut self.closures } @@ -245,12 +326,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)] @@ -282,9 +364,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() }) } @@ -315,7 +397,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 @@ -327,7 +412,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); } } @@ -401,6 +486,32 @@ impl ChildStdio { } } +#[unstable(feature = "command_sized", issue = "74549")] +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>()) + } + fn to_plain(&self) -> &OsStr { + self + } +} + +#[unstable(feature = "command_sized", issue = "74549")] +impl<'a, T> Arg for &'a T +where + T: Arg, +{ + fn arg_size(&self, _fq: bool) -> Result { + (*self).arg_size(_fq) + } + fn to_plain(&self) -> &OsStr { + (*self).to_plain() + } +} + 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 b19ad4ccdc777..de8935e8d5734 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_const( - io::ErrorKind::InvalidInput, - &"nul byte found in provided data", - )); - } + self.problem()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -36,11 +31,8 @@ impl Command { } pub fn exec(&mut self, default: Stdio) -> io::Error { - if self.saw_nul() { - return io::Error::new_const( - 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 ed9044382a898..81c6c9506308e 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -27,12 +27,7 @@ impl Command { let envp = self.capture_env(); - if self.saw_nul() { - return Err(io::Error::new_const( - ErrorKind::InvalidInput, - &"nul byte found in provided data", - )); - } + self.problem()?; let (ours, theirs) = self.setup_io(default, needs_stdin)?; @@ -114,11 +109,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_const( - 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/process.rs b/library/std/src/sys/windows/process.rs index a5799606142ec..2317a7c882bf0 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -10,7 +10,7 @@ use crate::env::split_paths; use crate::ffi::{OsStr, OsString}; use crate::fmt; use crate::fs; -use crate::io::{self, Error, ErrorKind}; +use crate::io::{self, Error}; use crate::mem; use crate::os::windows::ffi::OsStrExt; use crate::path::Path; @@ -23,7 +23,9 @@ use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; use crate::sys_common::mutex::StaticMutex; use crate::sys_common::process::{CommandEnv, CommandEnvs}; +pub use crate::sys_common::process_ext::{Arg, Problem}; use crate::sys_common::AsInner; +use core::convert::TryInto; use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS}; @@ -60,14 +62,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_const(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, @@ -79,6 +79,8 @@ pub struct Command { stdout: Option, stderr: Option, force_quotes_enabled: bool, + cmdline: Vec, + problem: Option, } pub enum Stdio { @@ -93,6 +95,9 @@ pub struct StdioPipes { pub stdout: Option, pub stderr: Option, } +/// 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 { @@ -107,11 +112,47 @@ impl Command { stdout: None, stderr: None, force_quotes_enabled: false, + cmdline: Vec::new(), + problem: None, + } + } + + 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, self.force_quotes_enabled); + 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 @@ -131,6 +172,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 force_quotes(&mut self, enabled: bool) { self.force_quotes_enabled = enabled; @@ -158,11 +206,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. @@ -177,15 +229,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, self.force_quotes_enabled)?; - cmd_str.push(0); // add null terminator - // stolen from the libuv code. let mut flags = self.flags | c::CREATE_UNICODE_ENVIRONMENT; if self.detach { @@ -224,8 +281,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, @@ -244,6 +301,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 { @@ -313,6 +385,35 @@ impl From for Stdio { } } +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +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) + } +} + +#[unstable(feature = "windows_raw_cmdline", issue = "74549")] +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 //////////////////////////////////////////////////////////////////////////////// @@ -355,7 +456,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))?; @@ -451,55 +552,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], force_quotes: bool) -> 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, force_quotes)?; + arg.as_os_str().append_to(&mut cmd, force_quotes)?; } 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)> { diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index 7fa6977f2af26..5cf2a544c2520 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -31,6 +31,9 @@ pub mod mutex; #[cfg(any(doc, not(windows)))] pub mod os_str_bytes; pub mod process; +#[cfg(any(unix, windows))] +#[macro_use] +pub(crate) mod process_ext; pub mod remutex; #[macro_use] pub mod rt; diff --git a/library/std/src/sys_common/process_ext.rs b/library/std/src/sys_common/process_ext.rs new file mode 100644 index 0000000000000..6f87cce472975 --- /dev/null +++ b/library/std/src/sys_common/process_ext.rs @@ -0,0 +1,169 @@ +// Public traits for CommandSized. May x.py have mercy on me. +use crate::ffi::OsStr; +use crate::io; + +#[cfg(any(windows, doc))] +use crate::ffi::OsString; + +/// Traits for handling a sized 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. + /// 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; +} + +/// Types that can be appended to a Windows command-line. Used for custom escaping. +/// +/// Do not implement this trait on Unix. Its existence is only due to the way CommandSized +/// is defined. +// FIXME: The force-quoted one should conceptually be its own type. Would be +// iseful for xargs. +#[unstable(feature = "command_sized", issue = "74549")] +pub trait Arg { + /// Calculate size of arg in a cmdline. + fn arg_size(&self, force_quotes: bool) -> Result; + #[cfg(any(unix))] + #[doc(cfg(unix))] + /// Convert to more palatable form for Unix. + fn to_plain(&self) -> &OsStr; + #[cfg(any(windows))] + #[doc(cfg(windows))] + /// Retain the argument by copying. Wait, why we are retaining it? + // FIXME: Isn't information already lost when we put it into the + // vector, erasing type info? Why do we still use the args vector? + // Okay, apparently we are putting it in a CommandArgs<>. Hmm. + fn to_os_string(&self) -> OsString; + #[cfg(any(windows))] + #[doc(cfg(windows))] + /// Append argument to a cmdline. + fn append_to(&self, cmd: &mut Vec, force_quotes: bool) -> Result; +} + +#[derive(Copy, Clone)] +#[unstable(feature = "command_sized", issue = "74549")] +pub enum Problem { + SawNul, + Oversized, +} + +#[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() + } +} + +/// Implementation for the above trait. +macro_rules! impl_command_sized { + (prelude) => { + use crate::sys::process::{Arg, Problem}; + use core::convert::TryFrom; + }; + (marg $marg_func:path) => { + fn maybe_arg(&mut self, arg: impl Arg) -> io::Result<&mut Self> { + $marg_func(self.as_inner_mut(), arg)?; + Ok(self) + } + }; + (margs $marg_func:path) => { + 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) = $marg_func(self.as_inner_mut(), arg) { + return Err((count, err)); + } + count += 1; + } + Ok(self) + } + }; + (xargs $args_func:path) => { + 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()); + } + + $args_func(&mut cmd, &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()); + } + $args_func(&mut cmd, &after); + ret.push(cmd); + cmd = Self::new(program); + $args_func(&mut cmd, &before); + } + cmd.maybe_arg(arg)?; + fresh = false; + } + $args_func(&mut cmd, &after); + ret.push(cmd); + Ok(ret) + } + }; +} diff --git a/library/stdarch b/library/stdarch index 6c4f4e1990b76..37d6e1886369e 160000 --- a/library/stdarch +++ b/library/stdarch @@ -1 +1 @@ -Subproject commit 6c4f4e1990b76be8a07bde1956d2e3452fd55ee4 +Subproject commit 37d6e1886369ea0176356286dc7fbd42ee5aa79c diff --git a/src/doc/book b/src/doc/book index 50dd06cb71beb..55a26488ddefc 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit 50dd06cb71beb27fdc0eebade5509cdcc1f821ed +Subproject commit 55a26488ddefc8433e73a2e8352d70f7a5c7fc2b diff --git a/src/doc/edition-guide b/src/doc/edition-guide index 1da3c411f17ad..302a115e8f718 160000 --- a/src/doc/edition-guide +++ b/src/doc/edition-guide @@ -1 +1 @@ -Subproject commit 1da3c411f17adb1ba5de1683bb6acee83362b54a +Subproject commit 302a115e8f71876dfc884aebb0ca5ccb02b8a962 diff --git a/src/doc/embedded-book b/src/doc/embedded-book index 569c3391f5c0c..7349d173fa28a 160000 --- a/src/doc/embedded-book +++ b/src/doc/embedded-book @@ -1 +1 @@ -Subproject commit 569c3391f5c0cc43433bc77831d17f8ff4d76602 +Subproject commit 7349d173fa28a0bb834cf0264a05286620ef0923 diff --git a/src/doc/nomicon b/src/doc/nomicon index 8551afbb2ca6f..55de6fa3c1f33 160000 --- a/src/doc/nomicon +++ b/src/doc/nomicon @@ -1 +1 @@ -Subproject commit 8551afbb2ca6f5ea37fe58380318b209785e4e02 +Subproject commit 55de6fa3c1f331774da19472c9ee57d2ae9eb039 diff --git a/src/doc/reference b/src/doc/reference index d23f9da846961..9c68af3ce6ccc 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit d23f9da8469617e6c81121d9fd123443df70595d +Subproject commit 9c68af3ce6ccca2395e1868addef26a0542e9ddd diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index e0a721f5202e6..805e016c5792a 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit e0a721f5202e6d9bec0aff99f10e44480c0da9e7 +Subproject commit 805e016c5792ad2adabb66e348233067d5ea9f10 diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide index e72b43a64925c..50de7f0682adc 160000 --- a/src/doc/rustc-dev-guide +++ b/src/doc/rustc-dev-guide @@ -1 +1 @@ -Subproject commit e72b43a64925ce053dc7830e21c1a57ba00499bd +Subproject commit 50de7f0682adc5d95ce858fe6318d19b4b951553 diff --git a/src/doc/unstable-book/src/library-features/command-sized.md b/src/doc/unstable-book/src/library-features/command-sized.md new file mode 100644 index 0000000000000..baafc739f12b5 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/command-sized.md @@ -0,0 +1,10 @@ +# `command_sized` + +The tracking issue for this feature is: [#74549] + +[#74549]: https://github.com/rust-lang/rust/issues/74549 + +------------------------ + +The `command_sized` feature adds a facility to consider the maximum size of +commands in `std::Process`. diff --git a/src/doc/unstable-book/src/library-features/windows-raw-cmdline.md b/src/doc/unstable-book/src/library-features/windows-raw-cmdline.md new file mode 100644 index 0000000000000..ee5c8135ddac0 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/windows-raw-cmdline.md @@ -0,0 +1,10 @@ +# `windows_raw_cmdline` + +The tracking issue for this feature is: [#74549] + +[#74549]: https://github.com/rust-lang/rust/issues/74549 + +------------------------ + +The `windows_raw_cmdline` feature adds the ability to pass in raw command-line +arguments on Windows via the `RawArg` type. diff --git a/src/llvm-project b/src/llvm-project index c78cf18a07f19..5f67a5715771b 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit c78cf18a07f19faa3e51f15220bca39f47d437e0 +Subproject commit 5f67a5715771b7d29e4713e8d68338602d216dcf diff --git a/src/tools/cargo b/src/tools/cargo index e51522ab3db23..e931e4796b61d 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit e51522ab3db23b0d8f1de54eb1f0113924896331 +Subproject commit e931e4796b61de593aa1097649445e535c9c7ee0 diff --git a/src/tools/miri b/src/tools/miri index 38b5f236d2c62..453affaaa1762 160000 --- a/src/tools/miri +++ b/src/tools/miri @@ -1 +1 @@ -Subproject commit 38b5f236d2c62ff0b1017efd183b193f5db33123 +Subproject commit 453affaaa1762a065d2857970b8333017211208c diff --git a/src/tools/rls b/src/tools/rls index e33f4e68496b2..9ed6f96f2ff85 160000 --- a/src/tools/rls +++ b/src/tools/rls @@ -1 +1 @@ -Subproject commit e33f4e68496b296dedb100e297dc4451f169b2b3 +Subproject commit 9ed6f96f2ff85753c5a6ac290ee88ecb2831ab2e diff --git a/src/tools/rust-analyzer b/src/tools/rust-analyzer index fd109fb587904..f4383981249d3 160000 --- a/src/tools/rust-analyzer +++ b/src/tools/rust-analyzer @@ -1 +1 @@ -Subproject commit fd109fb587904cfecc1149e068814bfd38feb83c +Subproject commit f4383981249d3f2964f2c667f3349f8ff15b77c4 diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index db177f75ceae9..f0aaacad24f88 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -38,6 +38,7 @@ const EXCEPTION_PATHS: &[&str] = &[ "library/panic_abort", "library/panic_unwind", "library/unwind", + "library/std/src/sys_common/process_ext.rs", "library/rtstartup", // Not sure what to do about this. magic stuff for mingw "library/term", // Not sure how to make this crate portable, but test crate needs it. "library/test", // Probably should defer to unstable `std::sys` APIs.