diff options
| author | Thalia Archibald <thalia@archibald.dev> | 2025-04-12 02:26:17 -0700 |
|---|---|---|
| committer | Thalia Archibald <thalia@archibald.dev> | 2025-04-12 03:10:21 -0700 |
| commit | 6ffebb65d6daf4fcc82cfc7fd14397bff98df1f2 (patch) | |
| tree | a0cf46afbbf6c9c39cd249a175520e2f5fe594bb /library/std/src/sys/args | |
| parent | 1bc56185ee257ed829a0aea7abdc3b03c5fed887 (diff) | |
| download | rust-6ffebb65d6daf4fcc82cfc7fd14397bff98df1f2.tar.gz rust-6ffebb65d6daf4fcc82cfc7fd14397bff98df1f2.zip | |
Move args into std::sys
Diffstat (limited to 'library/std/src/sys/args')
| -rw-r--r-- | library/std/src/sys/args/hermit.rs | 66 | ||||
| -rw-r--r-- | library/std/src/sys/args/mod.rs | 34 | ||||
| -rw-r--r-- | library/std/src/sys/args/sgx.rs | 61 | ||||
| -rw-r--r-- | library/std/src/sys/args/uefi.rs | 156 | ||||
| -rw-r--r-- | library/std/src/sys/args/unix.rs | 241 | ||||
| -rw-r--r-- | library/std/src/sys/args/unsupported.rs | 36 | ||||
| -rw-r--r-- | library/std/src/sys/args/wasi.rs | 61 | ||||
| -rw-r--r-- | library/std/src/sys/args/windows.rs | 442 | ||||
| -rw-r--r-- | library/std/src/sys/args/windows/tests.rs | 91 | ||||
| -rw-r--r-- | library/std/src/sys/args/xous.rs | 53 | ||||
| -rw-r--r-- | library/std/src/sys/args/zkvm.rs | 81 |
11 files changed, 1322 insertions, 0 deletions
diff --git a/library/std/src/sys/args/hermit.rs b/library/std/src/sys/args/hermit.rs new file mode 100644 index 00000000000..44024260277 --- /dev/null +++ b/library/std/src/sys/args/hermit.rs @@ -0,0 +1,66 @@ +use crate::ffi::{CStr, OsString, c_char}; +use crate::os::hermit::ffi::OsStringExt; +use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; +use crate::sync::atomic::{AtomicIsize, AtomicPtr}; +use crate::{fmt, ptr, vec}; + +static ARGC: AtomicIsize = AtomicIsize::new(0); +static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(ptr::null_mut()); + +/// One-time global initialization. +pub unsafe fn init(argc: isize, argv: *const *const u8) { + ARGC.store(argc, Relaxed); + // Use release ordering here to broadcast writes by the OS. + ARGV.store(argv as *mut *const u8, Release); +} + +/// Returns the command line arguments +pub fn args() -> Args { + // Synchronize with the store above. + let argv = ARGV.load(Acquire); + // If argv has not been initialized yet, do not return any arguments. + let argc = if argv.is_null() { 0 } else { ARGC.load(Relaxed) }; + let args: Vec<OsString> = (0..argc) + .map(|i| unsafe { + let cstr = CStr::from_ptr(*argv.offset(i) as *const c_char); + OsStringExt::from_vec(cstr.to_bytes().to_vec()) + }) + .collect(); + + Args { iter: args.into_iter() } +} + +pub struct Args { + iter: vec::IntoIter<OsString>, +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.iter.as_slice().fmt(f) + } +} + +impl !Send for Args {} +impl !Sync for Args {} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.iter.next_back() + } +} diff --git a/library/std/src/sys/args/mod.rs b/library/std/src/sys/args/mod.rs new file mode 100644 index 00000000000..6edcafe2c4c --- /dev/null +++ b/library/std/src/sys/args/mod.rs @@ -0,0 +1,34 @@ +//! Platform-dependent command line arguments abstraction. + +#![forbid(unsafe_op_in_unsafe_fn)] + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix; + pub use unix::*; + } else if #[cfg(target_family = "windows")] { + mod windows; + pub use windows::*; + } else if #[cfg(target_os = "hermit")] { + mod hermit; + pub use hermit::*; + } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + mod sgx; + pub use sgx::*; + } else if #[cfg(target_os = "uefi")] { + mod uefi; + pub use uefi::*; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + pub use wasi::*; + } else if #[cfg(target_os = "xous")] { + mod xous; + pub use xous::*; + } else if #[cfg(target_os = "zkvm")] { + mod zkvm; + pub use zkvm::*; + } else { + mod unsupported; + pub use unsupported::*; + } +} diff --git a/library/std/src/sys/args/sgx.rs b/library/std/src/sys/args/sgx.rs new file mode 100644 index 00000000000..efc4b791852 --- /dev/null +++ b/library/std/src/sys/args/sgx.rs @@ -0,0 +1,61 @@ +#![allow(fuzzy_provenance_casts)] // FIXME: this module systematically confuses pointers and integers + +use crate::ffi::OsString; +use crate::sync::atomic::{AtomicUsize, Ordering}; +use crate::sys::os_str::Buf; +use crate::sys::pal::abi::usercalls::alloc; +use crate::sys::pal::abi::usercalls::raw::ByteBuffer; +use crate::sys_common::FromInner; +use crate::{fmt, slice}; + +#[cfg_attr(test, linkage = "available_externally")] +#[unsafe(export_name = "_ZN16__rust_internals3std3sys3sgx4args4ARGSE")] +static ARGS: AtomicUsize = AtomicUsize::new(0); +type ArgsStore = Vec<OsString>; + +#[cfg_attr(test, allow(dead_code))] +pub unsafe fn init(argc: isize, argv: *const *const u8) { + if argc != 0 { + let args = unsafe { alloc::User::<[ByteBuffer]>::from_raw_parts(argv as _, argc as _) }; + let args = args + .iter() + .map(|a| OsString::from_inner(Buf { inner: a.copy_user_buffer() })) + .collect::<ArgsStore>(); + ARGS.store(Box::into_raw(Box::new(args)) as _, Ordering::Relaxed); + } +} + +pub fn args() -> Args { + let args = unsafe { (ARGS.load(Ordering::Relaxed) as *const ArgsStore).as_ref() }; + if let Some(args) = args { Args(args.iter()) } else { Args([].iter()) } +} + +pub struct Args(slice::Iter<'static, OsString>); + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.0.next().cloned() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.0.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.0.next_back().cloned() + } +} diff --git a/library/std/src/sys/args/uefi.rs b/library/std/src/sys/args/uefi.rs new file mode 100644 index 00000000000..4e7412937bd --- /dev/null +++ b/library/std/src/sys/args/uefi.rs @@ -0,0 +1,156 @@ +use r_efi::protocols::loaded_image; + +use crate::env::current_exe; +use crate::ffi::OsString; +use crate::iter::Iterator; +use crate::sys::pal::helpers; +use crate::{fmt, vec}; + +pub struct Args { + parsed_args_list: vec::IntoIter<OsString>, +} + +pub fn args() -> Args { + let lazy_current_exe = || Vec::from([current_exe().map(Into::into).unwrap_or_default()]); + + // Each loaded image has an image handle that supports `EFI_LOADED_IMAGE_PROTOCOL`. Thus, this + // will never fail. + let protocol = + helpers::image_handle_protocol::<loaded_image::Protocol>(loaded_image::PROTOCOL_GUID) + .unwrap(); + + let lp_size = unsafe { (*protocol.as_ptr()).load_options_size } as usize; + // Break if we are sure that it cannot be UTF-16 + if lp_size < size_of::<u16>() || lp_size % size_of::<u16>() != 0 { + return Args { parsed_args_list: lazy_current_exe().into_iter() }; + } + let lp_size = lp_size / size_of::<u16>(); + + let lp_cmd_line = unsafe { (*protocol.as_ptr()).load_options as *const u16 }; + if !lp_cmd_line.is_aligned() { + return Args { parsed_args_list: lazy_current_exe().into_iter() }; + } + let lp_cmd_line = unsafe { crate::slice::from_raw_parts(lp_cmd_line, lp_size) }; + + Args { + parsed_args_list: parse_lp_cmd_line(lp_cmd_line) + .unwrap_or_else(lazy_current_exe) + .into_iter(), + } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.parsed_args_list.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + + fn next(&mut self) -> Option<OsString> { + self.parsed_args_list.next() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.parsed_args_list.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.parsed_args_list.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.parsed_args_list.next_back() + } +} + +/// Implements the UEFI command-line argument parsing algorithm. +/// +/// This implementation is based on what is defined in Section 3.4 of +/// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf) +/// +/// Returns None in the following cases: +/// - Invalid UTF-16 (unpaired surrogate) +/// - Empty/improper arguments +fn parse_lp_cmd_line(code_units: &[u16]) -> Option<Vec<OsString>> { + const QUOTE: char = '"'; + const SPACE: char = ' '; + const CARET: char = '^'; + const NULL: char = '\0'; + + let mut ret_val = Vec::new(); + let mut code_units_iter = char::decode_utf16(code_units.iter().cloned()).peekable(); + + // The executable name at the beginning is special. + let mut in_quotes = false; + let mut cur = String::new(); + while let Some(w) = code_units_iter.next() { + let w = w.ok()?; + match w { + // break on NULL + NULL => break, + // A quote mark always toggles `in_quotes` no matter what because + // there are no escape characters when parsing the executable name. + QUOTE => in_quotes = !in_quotes, + // If not `in_quotes` then whitespace ends argv[0]. + SPACE if !in_quotes => break, + // In all other cases the code unit is taken literally. + _ => cur.push(w), + } + } + + // If exe name is missing, the cli args are invalid + if cur.is_empty() { + return None; + } + + ret_val.push(OsString::from(cur)); + // Skip whitespace. + while code_units_iter.next_if_eq(&Ok(SPACE)).is_some() {} + + // Parse the arguments according to these rules: + // * All code units are taken literally except space, quote and caret. + // * When not `in_quotes`, space separate arguments. Consecutive spaces are + // treated as a single separator. + // * A space `in_quotes` is taken literally. + // * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally. + // * A quote can be escaped if preceded by caret. + // * A caret can be escaped if preceded by caret. + let mut cur = String::new(); + let mut in_quotes = false; + while let Some(w) = code_units_iter.next() { + let w = w.ok()?; + match w { + // break on NULL + NULL => break, + // If not `in_quotes`, a space or tab ends the argument. + SPACE if !in_quotes => { + ret_val.push(OsString::from(&cur[..])); + cur.truncate(0); + + // Skip whitespace. + while code_units_iter.next_if_eq(&Ok(SPACE)).is_some() {} + } + // Caret can escape quotes or carets + CARET if in_quotes => { + if let Some(x) = code_units_iter.next() { + cur.push(x.ok()?); + } + } + // If quote then flip `in_quotes` + QUOTE => in_quotes = !in_quotes, + // Everything else is always taken literally. + _ => cur.push(w), + } + } + // Push the final argument, if any. + if !cur.is_empty() || in_quotes { + ret_val.push(OsString::from(cur)); + } + Some(ret_val) +} diff --git a/library/std/src/sys/args/unix.rs b/library/std/src/sys/args/unix.rs new file mode 100644 index 00000000000..4c30609c099 --- /dev/null +++ b/library/std/src/sys/args/unix.rs @@ -0,0 +1,241 @@ +//! Global initialization and retrieval of command line arguments. +//! +//! On some platforms these are stored during runtime startup, +//! and on some they are retrieved from the system on demand. + +#![allow(dead_code)] // runtime init functions not used during testing + +use crate::ffi::{CStr, OsString}; +use crate::os::unix::ffi::OsStringExt; +use crate::{fmt, vec}; + +/// One-time global initialization. +pub unsafe fn init(argc: isize, argv: *const *const u8) { + unsafe { imp::init(argc, argv) } +} + +/// Returns the command line arguments +pub fn args() -> Args { + let (argc, argv) = imp::argc_argv(); + + let mut vec = Vec::with_capacity(argc as usize); + + for i in 0..argc { + // SAFETY: `argv` is non-null if `argc` is positive, and it is + // guaranteed to be at least as long as `argc`, so reading from it + // should be safe. + let ptr = unsafe { argv.offset(i).read() }; + + // Some C commandline parsers (e.g. GLib and Qt) are replacing already + // handled arguments in `argv` with `NULL` and move them to the end. + // + // Since they can't directly ensure updates to `argc` as well, this + // means that `argc` might be bigger than the actual number of + // non-`NULL` pointers in `argv` at this point. + // + // To handle this we simply stop iterating at the first `NULL` + // argument. `argv` is also guaranteed to be `NULL`-terminated so any + // non-`NULL` arguments after the first `NULL` can safely be ignored. + if ptr.is_null() { + // NOTE: On Apple platforms, `-[NSProcessInfo arguments]` does not + // stop iterating here, but instead `continue`, always iterating + // up until it reached `argc`. + // + // This difference will only matter in very specific circumstances + // where `argc`/`argv` have been modified, but in unexpected ways, + // so it likely doesn't really matter which option we choose. + // See the following PR for further discussion: + // <https://github.com/rust-lang/rust/pull/125225> + break; + } + + // SAFETY: Just checked that the pointer is not NULL, and arguments + // are otherwise guaranteed to be valid C strings. + let cstr = unsafe { CStr::from_ptr(ptr) }; + vec.push(OsStringExt::from_vec(cstr.to_bytes().to_vec())); + } + + Args { iter: vec.into_iter() } +} + +pub struct Args { + iter: vec::IntoIter<OsString>, +} + +impl !Send for Args {} +impl !Sync for Args {} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.iter.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.iter.next_back() + } +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", + target_os = "solaris", + target_os = "illumos", + target_os = "emscripten", + target_os = "haiku", + target_os = "l4re", + target_os = "fuchsia", + target_os = "redox", + target_os = "vxworks", + target_os = "horizon", + target_os = "aix", + target_os = "nto", + target_os = "hurd", + target_os = "rtems", + target_os = "nuttx", +))] +mod imp { + use crate::ffi::c_char; + use crate::ptr; + use crate::sync::atomic::{AtomicIsize, AtomicPtr, Ordering}; + + // The system-provided argc and argv, which we store in static memory + // here so that we can defer the work of parsing them until its actually + // needed. + // + // Note that we never mutate argv/argc, the argv array, or the argv + // strings, which allows the code in this file to be very simple. + static ARGC: AtomicIsize = AtomicIsize::new(0); + static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(ptr::null_mut()); + + unsafe fn really_init(argc: isize, argv: *const *const u8) { + // These don't need to be ordered with each other or other stores, + // because they only hold the unmodified system-provide argv/argc. + ARGC.store(argc, Ordering::Relaxed); + ARGV.store(argv as *mut _, Ordering::Relaxed); + } + + #[inline(always)] + pub unsafe fn init(argc: isize, argv: *const *const u8) { + // on GNU/Linux if we are main then we will init argv and argc twice, it "duplicates work" + // BUT edge-cases are real: only using .init_array can break most emulators, dlopen, etc. + unsafe { really_init(argc, argv) }; + } + + /// glibc passes argc, argv, and envp to functions in .init_array, as a non-standard extension. + /// This allows `std::env::args` to work even in a `cdylib`, as it does on macOS and Windows. + #[cfg(all(target_os = "linux", target_env = "gnu"))] + #[used] + #[unsafe(link_section = ".init_array.00099")] + static ARGV_INIT_ARRAY: extern "C" fn( + crate::os::raw::c_int, + *const *const u8, + *const *const u8, + ) = { + extern "C" fn init_wrapper( + argc: crate::os::raw::c_int, + argv: *const *const u8, + _envp: *const *const u8, + ) { + unsafe { really_init(argc as isize, argv) }; + } + init_wrapper + }; + + pub fn argc_argv() -> (isize, *const *const c_char) { + // Load ARGC and ARGV, which hold the unmodified system-provided + // argc/argv, so we can read the pointed-to memory without atomics or + // synchronization. + // + // If either ARGC or ARGV is still zero or null, then either there + // really are no arguments, or someone is asking for `args()` before + // initialization has completed, and we return an empty list. + let argv = ARGV.load(Ordering::Relaxed); + let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) }; + + // Cast from `*mut *const u8` to `*const *const c_char` + (argc, argv.cast()) + } +} + +// Use `_NSGetArgc` and `_NSGetArgv` on Apple platforms. +// +// Even though these have underscores in their names, they've been available +// since the first versions of both macOS and iOS, and are declared in +// the header `crt_externs.h`. +// +// NOTE: This header was added to the iOS 13.0 SDK, which has been the source +// of a great deal of confusion in the past about the availability of these +// APIs. +// +// NOTE(madsmtm): This has not strictly been verified to not cause App Store +// rejections; if this is found to be the case, the previous implementation +// of this used `[[NSProcessInfo processInfo] arguments]`. +#[cfg(target_vendor = "apple")] +mod imp { + use crate::ffi::{c_char, c_int}; + + pub unsafe fn init(_argc: isize, _argv: *const *const u8) { + // No need to initialize anything in here, `libdyld.dylib` has already + // done the work for us. + } + + pub fn argc_argv() -> (isize, *const *const c_char) { + unsafe extern "C" { + // These functions are in crt_externs.h. + fn _NSGetArgc() -> *mut c_int; + fn _NSGetArgv() -> *mut *mut *mut c_char; + } + + // SAFETY: The returned pointer points to a static initialized early + // in the program lifetime by `libdyld.dylib`, and as such is always + // valid. + // + // NOTE: Similar to `_NSGetEnviron`, there technically isn't anything + // protecting us against concurrent modifications to this, and there + // doesn't exist a lock that we can take. Instead, it is generally + // expected that it's only modified in `main` / before other code + // runs, so reading this here should be fine. + let argc = unsafe { _NSGetArgc().read() }; + // SAFETY: Same as above. + let argv = unsafe { _NSGetArgv().read() }; + + // Cast from `*mut *mut c_char` to `*const *const c_char` + (argc as isize, argv.cast()) + } +} + +#[cfg(any(target_os = "espidf", target_os = "vita"))] +mod imp { + use crate::ffi::c_char; + use crate::ptr; + + #[inline(always)] + pub unsafe fn init(_argc: isize, _argv: *const *const u8) {} + + pub fn argc_argv() -> (isize, *const *const c_char) { + (0, ptr::null()) + } +} diff --git a/library/std/src/sys/args/unsupported.rs b/library/std/src/sys/args/unsupported.rs new file mode 100644 index 00000000000..a2d75a61976 --- /dev/null +++ b/library/std/src/sys/args/unsupported.rs @@ -0,0 +1,36 @@ +use crate::ffi::OsString; +use crate::fmt; + +pub struct Args {} + +pub fn args() -> Args { + Args {} +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().finish() + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + None + } + fn size_hint(&self) -> (usize, Option<usize>) { + (0, Some(0)) + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + 0 + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + None + } +} diff --git a/library/std/src/sys/args/wasi.rs b/library/std/src/sys/args/wasi.rs new file mode 100644 index 00000000000..52cfa202af8 --- /dev/null +++ b/library/std/src/sys/args/wasi.rs @@ -0,0 +1,61 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +use crate::ffi::{CStr, OsStr, OsString}; +use crate::os::wasi::ffi::OsStrExt; +use crate::{fmt, vec}; + +pub struct Args { + iter: vec::IntoIter<OsString>, +} + +impl !Send for Args {} +impl !Sync for Args {} + +/// Returns the command line arguments +pub fn args() -> Args { + Args { iter: maybe_args().unwrap_or(Vec::new()).into_iter() } +} + +fn maybe_args() -> Option<Vec<OsString>> { + unsafe { + let (argc, buf_size) = wasi::args_sizes_get().ok()?; + let mut argv = Vec::with_capacity(argc); + let mut buf = Vec::with_capacity(buf_size); + wasi::args_get(argv.as_mut_ptr(), buf.as_mut_ptr()).ok()?; + argv.set_len(argc); + let mut ret = Vec::with_capacity(argc); + for ptr in argv { + let s = CStr::from_ptr(ptr.cast()); + ret.push(OsStr::from_bytes(s.to_bytes()).to_owned()); + } + Some(ret) + } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.iter.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.iter.next_back() + } +} diff --git a/library/std/src/sys/args/windows.rs b/library/std/src/sys/args/windows.rs new file mode 100644 index 00000000000..55bd6864cae --- /dev/null +++ b/library/std/src/sys/args/windows.rs @@ -0,0 +1,442 @@ +//! The Windows command line is just a string +//! <https://docs.microsoft.com/en-us/archive/blogs/larryosterman/the-windows-command-line-is-just-a-string> +//! +//! This module implements the parsing necessary to turn that string into a list of arguments. + +#[cfg(test)] +mod tests; + +use crate::ffi::{OsStr, OsString}; +use crate::num::NonZero; +use crate::os::windows::prelude::*; +use crate::path::{Path, PathBuf}; +use crate::sys::pal::os::current_exe; +use crate::sys::pal::{ensure_no_nuls, fill_utf16_buf}; +use crate::sys::path::get_long_path; +use crate::sys::{c, to_u16s}; +use crate::sys_common::AsInner; +use crate::sys_common::wstr::WStrUnits; +use crate::{fmt, io, iter, ptr, vec}; + +pub fn args() -> Args { + // SAFETY: `GetCommandLineW` returns a pointer to a null terminated UTF-16 + // string so it's safe for `WStrUnits` to use. + unsafe { + let lp_cmd_line = c::GetCommandLineW(); + let parsed_args_list = parse_lp_cmd_line(WStrUnits::new(lp_cmd_line), || { + current_exe().map(PathBuf::into_os_string).unwrap_or_else(|_| OsString::new()) + }); + + Args { parsed_args_list: parsed_args_list.into_iter() } + } +} + +/// Implements the Windows command-line argument parsing algorithm. +/// +/// Microsoft's documentation for the Windows CLI argument format can be found at +/// <https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments> +/// +/// A more in-depth explanation is here: +/// <https://daviddeley.com/autohotkey/parameters/parameters.htm#WIN> +/// +/// Windows includes a function to do command line parsing in shell32.dll. +/// However, this is not used for two reasons: +/// +/// 1. Linking with that DLL causes the process to be registered as a GUI application. +/// GUI applications add a bunch of overhead, even if no windows are drawn. See +/// <https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/>. +/// +/// 2. It does not follow the modern C/C++ argv rules outlined in the first two links above. +/// +/// This function was tested for equivalence to the C/C++ parsing rules using an +/// extensive test suite available at +/// <https://github.com/ChrisDenton/winarg/tree/std>. +fn parse_lp_cmd_line<'a, F: Fn() -> OsString>( + lp_cmd_line: Option<WStrUnits<'a>>, + exe_name: F, +) -> Vec<OsString> { + const BACKSLASH: NonZero<u16> = NonZero::new(b'\\' as u16).unwrap(); + const QUOTE: NonZero<u16> = NonZero::new(b'"' as u16).unwrap(); + const TAB: NonZero<u16> = NonZero::new(b'\t' as u16).unwrap(); + const SPACE: NonZero<u16> = NonZero::new(b' ' as u16).unwrap(); + + let mut ret_val = Vec::new(); + // If the cmd line pointer is null or it points to an empty string then + // return the name of the executable as argv[0]. + if lp_cmd_line.as_ref().and_then(|cmd| cmd.peek()).is_none() { + ret_val.push(exe_name()); + return ret_val; + } + let mut code_units = lp_cmd_line.unwrap(); + + // The executable name at the beginning is special. + let mut in_quotes = false; + let mut cur = Vec::new(); + for w in &mut code_units { + match w { + // A quote mark always toggles `in_quotes` no matter what because + // there are no escape characters when parsing the executable name. + QUOTE => in_quotes = !in_quotes, + // If not `in_quotes` then whitespace ends argv[0]. + SPACE | TAB if !in_quotes => break, + // In all other cases the code unit is taken literally. + _ => cur.push(w.get()), + } + } + // Skip whitespace. + code_units.advance_while(|w| w == SPACE || w == TAB); + ret_val.push(OsString::from_wide(&cur)); + + // Parse the arguments according to these rules: + // * All code units are taken literally except space, tab, quote and backslash. + // * When not `in_quotes`, space and tab separate arguments. Consecutive spaces and tabs are + // treated as a single separator. + // * A space or tab `in_quotes` is taken literally. + // * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally. + // * A quote can be escaped if preceded by an odd number of backslashes. + // * If any number of backslashes is immediately followed by a quote then the number of + // backslashes is halved (rounding down). + // * Backslashes not followed by a quote are all taken literally. + // * If `in_quotes` then a quote can also be escaped using another quote + // (i.e. two consecutive quotes become one literal quote). + let mut cur = Vec::new(); + let mut in_quotes = false; + while let Some(w) = code_units.next() { + match w { + // If not `in_quotes`, a space or tab ends the argument. + SPACE | TAB if !in_quotes => { + ret_val.push(OsString::from_wide(&cur[..])); + cur.truncate(0); + + // Skip whitespace. + code_units.advance_while(|w| w == SPACE || w == TAB); + } + // Backslashes can escape quotes or backslashes but only if consecutive backslashes are followed by a quote. + BACKSLASH => { + let backslash_count = code_units.advance_while(|w| w == BACKSLASH) + 1; + if code_units.peek() == Some(QUOTE) { + cur.extend(iter::repeat(BACKSLASH.get()).take(backslash_count / 2)); + // The quote is escaped if there are an odd number of backslashes. + if backslash_count % 2 == 1 { + code_units.next(); + cur.push(QUOTE.get()); + } + } else { + // If there is no quote on the end then there is no escaping. + cur.extend(iter::repeat(BACKSLASH.get()).take(backslash_count)); + } + } + // If `in_quotes` and not backslash escaped (see above) then a quote either + // unsets `in_quote` or is escaped by another quote. + QUOTE if in_quotes => match code_units.peek() { + // Two consecutive quotes when `in_quotes` produces one literal quote. + Some(QUOTE) => { + cur.push(QUOTE.get()); + code_units.next(); + } + // Otherwise set `in_quotes`. + Some(_) => in_quotes = false, + // The end of the command line. + // Push `cur` even if empty, which we do by breaking while `in_quotes` is still set. + None => break, + }, + // If not `in_quotes` and not BACKSLASH escaped (see above) then a quote sets `in_quote`. + QUOTE => in_quotes = true, + // Everything else is always taken literally. + _ => cur.push(w.get()), + } + } + // Push the final argument, if any. + if !cur.is_empty() || in_quotes { + ret_val.push(OsString::from_wide(&cur[..])); + } + ret_val +} + +pub struct Args { + parsed_args_list: vec::IntoIter<OsString>, +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.parsed_args_list.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.parsed_args_list.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.parsed_args_list.size_hint() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.parsed_args_list.next_back() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.parsed_args_list.len() + } +} + +#[derive(Debug)] +pub(crate) enum Arg { + /// Add quotes (if needed) + Regular(OsString), + /// Append raw string without quoting + Raw(OsString), +} + +enum Quote { + // Every arg is quoted + Always, + // Whitespace and empty args are quoted + Auto, + // Arg appended without any changes (#29494) + Never, +} + +pub(crate) fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> io::Result<()> { + let (arg, quote) = match arg { + Arg::Regular(arg) => (arg, if force_quotes { Quote::Always } else { Quote::Auto }), + Arg::Raw(arg) => (arg, Quote::Never), + }; + + // 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_encoded_bytes(); + let (quote, escape) = match quote { + Quote::Always => (true, true), + Quote::Auto => { + (arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(), true) + } + Quote::Never => (false, false), + }; + if quote { + cmd.push('"' as u16); + } + + let mut backslashes: usize = 0; + for x in arg.encode_wide() { + if escape { + 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 append_bat_arg(cmd: &mut Vec<u16>, arg: &OsStr, mut quote: bool) -> io::Result<()> { + ensure_no_nuls(arg)?; + // 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. + // + // We also need to quote the argument if it ends with `\` to guard against + // bat usage such as `"%~2"` (i.e. force quote arguments) otherwise a + // trailing slash will escape the closing quote. + if arg.is_empty() || arg.as_encoded_bytes().last() == Some(&b'\\') { + quote = true; + } + for cp in arg.as_inner().inner.code_points() { + if let Some(cp) = cp.to_char() { + // Rather than trying to find every ascii symbol that must be quoted, + // we assume that all ascii symbols must be quoted unless they're known to be good. + // We also quote Unicode control blocks for good measure. + // Note an unquoted `\` is fine so long as the argument isn't otherwise quoted. + static UNQUOTED: &str = r"#$*+-./:?@\_"; + let ascii_needs_quotes = + cp.is_ascii() && !(cp.is_ascii_alphanumeric() || UNQUOTED.contains(cp)); + if ascii_needs_quotes || cp.is_control() { + quote = true; + } + } + } + + if quote { + cmd.push('"' as u16); + } + // Loop through the string, escaping `\` only if followed by `"`. + // And escaping `"` by doubling them. + let mut backslashes: usize = 0; + for x in arg.encode_wide() { + if x == '\\' as u16 { + backslashes += 1; + } else { + if x == '"' as u16 { + // Add n backslashes to total 2n before internal `"`. + cmd.extend((0..backslashes).map(|_| '\\' as u16)); + // Appending an additional double-quote acts as an escape. + cmd.push(b'"' as u16) + } else if x == '%' as u16 || x == '\r' as u16 { + // yt-dlp hack: replaces `%` with `%%cd:~,%` to stop %VAR% being expanded as an environment variable. + // + // # Explanation + // + // cmd supports extracting a substring from a variable using the following syntax: + // %variable:~start_index,end_index% + // + // In the above command `cd` is used as the variable and the start_index and end_index are left blank. + // `cd` is a built-in variable that dynamically expands to the current directory so it's always available. + // Explicitly omitting both the start and end index creates a zero-length substring. + // + // Therefore it all resolves to nothing. However, by doing this no-op we distract cmd.exe + // from potentially expanding %variables% in the argument. + cmd.extend_from_slice(&[ + '%' as u16, '%' as u16, 'c' as u16, 'd' as u16, ':' as u16, '~' as u16, + ',' 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(()) +} + +pub(crate) fn make_bat_command_line( + script: &[u16], + args: &[Arg], + force_quotes: bool, +) -> io::Result<Vec<u16>> { + const INVALID_ARGUMENT_ERROR: io::Error = + io::const_error!(io::ErrorKind::InvalidInput, r#"batch file arguments are invalid"#); + // Set the start of the command line to `cmd.exe /c "` + // It is necessary to surround the command in an extra pair of quotes, + // hence the trailing quote here. It will be closed after all arguments + // have been added. + // Using /e:ON enables "command extensions" which is essential for the `%` hack to work. + let mut cmd: Vec<u16> = "cmd.exe /e:ON /v:OFF /d /c \"".encode_utf16().collect(); + + // Push the script name surrounded by its quote pair. + cmd.push(b'"' as u16); + // Windows file names cannot contain a `"` character or end with `\\`. + // If the script name does then return an error. + if script.contains(&(b'"' as u16)) || script.last() == Some(&(b'\\' as u16)) { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "Windows file names may not contain `\"` or end with `\\`" + )); + } + cmd.extend_from_slice(script.strip_suffix(&[0]).unwrap_or(script)); + cmd.push(b'"' as u16); + + // Append the arguments. + // FIXME: This needs tests to ensure that the arguments are properly + // reconstructed by the batch script by default. + for arg in args { + cmd.push(' ' as u16); + match arg { + Arg::Regular(arg_os) => { + let arg_bytes = arg_os.as_encoded_bytes(); + // Disallow \r and \n as they may truncate the arguments. + const DISALLOWED: &[u8] = b"\r\n"; + if arg_bytes.iter().any(|c| DISALLOWED.contains(c)) { + return Err(INVALID_ARGUMENT_ERROR); + } + append_bat_arg(&mut cmd, arg_os, force_quotes)?; + } + _ => { + // Raw arguments are passed on as-is. + // It's the user's responsibility to properly handle arguments in this case. + append_arg(&mut cmd, arg, force_quotes)?; + } + }; + } + + // Close the quote we left opened earlier. + cmd.push(b'"' as u16); + + Ok(cmd) +} + +/// Takes a path and tries to return a non-verbatim path. +/// +/// This is necessary because cmd.exe does not support verbatim paths. +pub(crate) fn to_user_path(path: &Path) -> io::Result<Vec<u16>> { + from_wide_to_user_path(to_u16s(path)?) +} +pub(crate) fn from_wide_to_user_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> { + // UTF-16 encoded code points, used in parsing and building UTF-16 paths. + // All of these are in the ASCII range so they can be cast directly to `u16`. + const SEP: u16 = b'\\' as _; + const QUERY: u16 = b'?' as _; + const COLON: u16 = b':' as _; + const U: u16 = b'U' as _; + const N: u16 = b'N' as _; + const C: u16 = b'C' as _; + + // Early return if the path is too long to remove the verbatim prefix. + const LEGACY_MAX_PATH: usize = 260; + if path.len() > LEGACY_MAX_PATH { + return Ok(path); + } + + match &path[..] { + // `\\?\C:\...` => `C:\...` + [SEP, SEP, QUERY, SEP, _, COLON, SEP, ..] => unsafe { + let lpfilename = path[4..].as_ptr(); + fill_utf16_buf( + |buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()), + |full_path: &[u16]| { + if full_path == &path[4..path.len() - 1] { + let mut path: Vec<u16> = full_path.into(); + path.push(0); + path + } else { + path + } + }, + ) + }, + // `\\?\UNC\...` => `\\...` + [SEP, SEP, QUERY, SEP, U, N, C, SEP, ..] => unsafe { + // Change the `C` in `UNC\` to `\` so we can get a slice that starts with `\\`. + path[6] = b'\\' as u16; + let lpfilename = path[6..].as_ptr(); + fill_utf16_buf( + |buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()), + |full_path: &[u16]| { + if full_path == &path[6..path.len() - 1] { + let mut path: Vec<u16> = full_path.into(); + path.push(0); + path + } else { + // Restore the 'C' in "UNC". + path[6] = b'C' as u16; + path + } + }, + ) + }, + // For everything else, leave the path unchanged. + _ => get_long_path(path, false), + } +} diff --git a/library/std/src/sys/args/windows/tests.rs b/library/std/src/sys/args/windows/tests.rs new file mode 100644 index 00000000000..484a90ab056 --- /dev/null +++ b/library/std/src/sys/args/windows/tests.rs @@ -0,0 +1,91 @@ +use super::*; +use crate::ffi::OsString; + +fn chk(string: &str, parts: &[&str]) { + let mut wide: Vec<u16> = OsString::from(string).encode_wide().collect(); + wide.push(0); + let parsed = + unsafe { parse_lp_cmd_line(WStrUnits::new(wide.as_ptr()), || OsString::from("TEST.EXE")) }; + let expected: Vec<OsString> = parts.iter().map(|k| OsString::from(k)).collect(); + assert_eq!(parsed.as_slice(), expected.as_slice(), "{:?}", string); +} + +#[test] +fn empty() { + chk("", &["TEST.EXE"]); + chk("\0", &["TEST.EXE"]); +} + +#[test] +fn single_words() { + chk("EXE one_word", &["EXE", "one_word"]); + chk("EXE a", &["EXE", "a"]); + chk("EXE 😅", &["EXE", "😅"]); + chk("EXE 😅🤦", &["EXE", "😅🤦"]); +} + +#[test] +fn official_examples() { + chk(r#"EXE "abc" d e"#, &["EXE", "abc", "d", "e"]); + chk(r#"EXE a\\\b d"e f"g h"#, &["EXE", r"a\\\b", "de fg", "h"]); + chk(r#"EXE a\\\"b c d"#, &["EXE", r#"a\"b"#, "c", "d"]); + chk(r#"EXE a\\\\"b c" d e"#, &["EXE", r"a\\b c", "d", "e"]); +} + +#[test] +fn whitespace_behavior() { + chk(" test", &["", "test"]); + chk(" test", &["", "test"]); + chk(" test test2", &["", "test", "test2"]); + chk(" test test2", &["", "test", "test2"]); + chk("test test2 ", &["test", "test2"]); + chk("test test2 ", &["test", "test2"]); + chk("test ", &["test"]); +} + +#[test] +fn genius_quotes() { + chk(r#"EXE "" """#, &["EXE", "", ""]); + chk(r#"EXE "" """"#, &["EXE", "", r#"""#]); + chk( + r#"EXE "this is """all""" in the same argument""#, + &["EXE", r#"this is "all" in the same argument"#], + ); + chk(r#"EXE "a"""#, &["EXE", r#"a""#]); + chk(r#"EXE "a"" a"#, &["EXE", r#"a" a"#]); + // quotes cannot be escaped in command names + chk(r#""EXE" check"#, &["EXE", "check"]); + chk(r#""EXE check""#, &["EXE check"]); + chk(r#""EXE """for""" check"#, &["EXE for check"]); + chk(r#""EXE \"for\" check"#, &[r"EXE \for\ check"]); + chk(r#""EXE \" for \" check"#, &[r"EXE \", "for", r#"""#, "check"]); + chk(r#"E"X"E test"#, &["EXE", "test"]); + chk(r#"EX""E test"#, &["EXE", "test"]); +} + +// from https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX +#[test] +fn post_2008() { + chk("EXE CallMeIshmael", &["EXE", "CallMeIshmael"]); + chk(r#"EXE "Call Me Ishmael""#, &["EXE", "Call Me Ishmael"]); + chk(r#"EXE Cal"l Me I"shmael"#, &["EXE", "Call Me Ishmael"]); + chk(r#"EXE CallMe\"Ishmael"#, &["EXE", r#"CallMe"Ishmael"#]); + chk(r#"EXE "CallMe\"Ishmael""#, &["EXE", r#"CallMe"Ishmael"#]); + chk(r#"EXE "Call Me Ishmael\\""#, &["EXE", r"Call Me Ishmael\"]); + chk(r#"EXE "CallMe\\\"Ishmael""#, &["EXE", r#"CallMe\"Ishmael"#]); + chk(r#"EXE a\\\b"#, &["EXE", r"a\\\b"]); + chk(r#"EXE "a\\\b""#, &["EXE", r"a\\\b"]); + chk(r#"EXE "\"Call Me Ishmael\"""#, &["EXE", r#""Call Me Ishmael""#]); + chk(r#"EXE "C:\TEST A\\""#, &["EXE", r"C:\TEST A\"]); + chk(r#"EXE "\"C:\TEST A\\\"""#, &["EXE", r#""C:\TEST A\""#]); + chk(r#"EXE "a b c" d e"#, &["EXE", "a b c", "d", "e"]); + chk(r#"EXE "ab\"c" "\\" d"#, &["EXE", r#"ab"c"#, r"\", "d"]); + chk(r#"EXE a\\\b d"e f"g h"#, &["EXE", r"a\\\b", "de fg", "h"]); + chk(r#"EXE a\\\"b c d"#, &["EXE", r#"a\"b"#, "c", "d"]); + chk(r#"EXE a\\\\"b c" d e"#, &["EXE", r"a\\b c", "d", "e"]); + // Double Double Quotes + chk(r#"EXE "a b c"""#, &["EXE", r#"a b c""#]); + chk(r#"EXE """CallMeIshmael""" b c"#, &["EXE", r#""CallMeIshmael""#, "b", "c"]); + chk(r#"EXE """Call Me Ishmael""""#, &["EXE", r#""Call Me Ishmael""#]); + chk(r#"EXE """"Call Me Ishmael"" b c"#, &["EXE", r#""Call"#, "Me", "Ishmael", "b", "c"]); +} diff --git a/library/std/src/sys/args/xous.rs b/library/std/src/sys/args/xous.rs new file mode 100644 index 00000000000..2dd37d53422 --- /dev/null +++ b/library/std/src/sys/args/xous.rs @@ -0,0 +1,53 @@ +use crate::ffi::OsString; +use crate::sys::pal::os::get_application_parameters; +use crate::sys::pal::os::params::ArgumentList; +use crate::{fmt, vec}; + +pub struct Args { + parsed_args_list: vec::IntoIter<OsString>, +} + +pub fn args() -> Args { + let Some(params) = get_application_parameters() else { + return Args { parsed_args_list: vec![].into_iter() }; + }; + + for param in params { + if let Ok(args) = ArgumentList::try_from(¶m) { + let mut parsed_args = vec![]; + for arg in args { + parsed_args.push(arg.into()); + } + return Args { parsed_args_list: parsed_args.into_iter() }; + } + } + Args { parsed_args_list: vec![].into_iter() } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.parsed_args_list.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.parsed_args_list.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.parsed_args_list.size_hint() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.parsed_args_list.next_back() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.parsed_args_list.len() + } +} diff --git a/library/std/src/sys/args/zkvm.rs b/library/std/src/sys/args/zkvm.rs new file mode 100644 index 00000000000..194ba7159d4 --- /dev/null +++ b/library/std/src/sys/args/zkvm.rs @@ -0,0 +1,81 @@ +use crate::ffi::OsString; +use crate::fmt; +use crate::sys::os_str; +use crate::sys::pal::{WORD_SIZE, abi}; +use crate::sys_common::FromInner; + +pub struct Args { + i_forward: usize, + i_back: usize, + count: usize, +} + +pub fn args() -> Args { + let count = unsafe { abi::sys_argc() }; + Args { i_forward: 0, i_back: 0, count } +} + +impl Args { + /// Use sys_argv to get the arg at the requested index. Does not check that i is less than argc + /// and will not return if the index is out of bounds. + fn argv(i: usize) -> OsString { + let arg_len = unsafe { abi::sys_argv(crate::ptr::null_mut(), 0, i) }; + + let arg_len_words = (arg_len + WORD_SIZE - 1) / WORD_SIZE; + let words = unsafe { abi::sys_alloc_words(arg_len_words) }; + + let arg_len2 = unsafe { abi::sys_argv(words, arg_len_words, i) }; + debug_assert_eq!(arg_len, arg_len2); + + // Convert to OsString. + // + // FIXME: We can probably get rid of the extra copy here if we + // reimplement "os_str" instead of just using the generic unix + // "os_str". + let arg_bytes: &[u8] = + unsafe { crate::slice::from_raw_parts(words.cast() as *const u8, arg_len) }; + OsString::from_inner(os_str::Buf { inner: arg_bytes.to_vec() }) + } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().finish() + } +} + +impl Iterator for Args { + type Item = OsString; + + fn next(&mut self) -> Option<OsString> { + if self.i_forward >= self.count - self.i_back { + None + } else { + let arg = Self::argv(self.i_forward); + self.i_forward += 1; + Some(arg) + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.count, Some(self.count)) + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.count + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + if self.i_back >= self.count - self.i_forward { + None + } else { + let arg = Self::argv(self.count - 1 - self.i_back); + self.i_back += 1; + Some(arg) + } + } +} |
