about summary refs log tree commit diff
path: root/library/std/src/sys/args
diff options
context:
space:
mode:
authorThalia Archibald <thalia@archibald.dev>2025-04-12 02:26:17 -0700
committerThalia Archibald <thalia@archibald.dev>2025-04-12 03:10:21 -0700
commit6ffebb65d6daf4fcc82cfc7fd14397bff98df1f2 (patch)
treea0cf46afbbf6c9c39cd249a175520e2f5fe594bb /library/std/src/sys/args
parent1bc56185ee257ed829a0aea7abdc3b03c5fed887 (diff)
downloadrust-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.rs66
-rw-r--r--library/std/src/sys/args/mod.rs34
-rw-r--r--library/std/src/sys/args/sgx.rs61
-rw-r--r--library/std/src/sys/args/uefi.rs156
-rw-r--r--library/std/src/sys/args/unix.rs241
-rw-r--r--library/std/src/sys/args/unsupported.rs36
-rw-r--r--library/std/src/sys/args/wasi.rs61
-rw-r--r--library/std/src/sys/args/windows.rs442
-rw-r--r--library/std/src/sys/args/windows/tests.rs91
-rw-r--r--library/std/src/sys/args/xous.rs53
-rw-r--r--library/std/src/sys/args/zkvm.rs81
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(&param) {
+            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)
+        }
+    }
+}