std/sys/args/unix.rs
1//! Global initialization and retrieval of command line arguments.
2//!
3//! On some platforms these are stored during runtime startup,
4//! and on some they are retrieved from the system on demand.
5
6#![allow(dead_code)] // runtime init functions not used during testing
7
8use crate::ffi::CStr;
9#[cfg(target_os = "hermit")]
10use crate::os::hermit::ffi::OsStringExt;
11#[cfg(not(target_os = "hermit"))]
12use crate::os::unix::ffi::OsStringExt;
13
14#[path = "common.rs"]
15mod common;
16pub use common::Args;
17
18/// One-time global initialization.
19pub unsafe fn init(argc: isize, argv: *const *const u8) {
20 unsafe { imp::init(argc, argv) }
21}
22
23/// Returns the command line arguments
24pub fn args() -> Args {
25 let (argc, argv) = imp::argc_argv();
26
27 let mut vec = Vec::with_capacity(argc as usize);
28
29 for i in 0..argc {
30 // SAFETY: `argv` is non-null if `argc` is positive, and it is
31 // guaranteed to be at least as long as `argc`, so reading from it
32 // should be safe.
33 let ptr = unsafe { argv.offset(i).read() };
34
35 // Some C commandline parsers (e.g. GLib and Qt) are replacing already
36 // handled arguments in `argv` with `NULL` and move them to the end.
37 //
38 // Since they can't directly ensure updates to `argc` as well, this
39 // means that `argc` might be bigger than the actual number of
40 // non-`NULL` pointers in `argv` at this point.
41 //
42 // To handle this we simply stop iterating at the first `NULL`
43 // argument. `argv` is also guaranteed to be `NULL`-terminated so any
44 // non-`NULL` arguments after the first `NULL` can safely be ignored.
45 if ptr.is_null() {
46 // NOTE: On Apple platforms, `-[NSProcessInfo arguments]` does not
47 // stop iterating here, but instead `continue`, always iterating
48 // up until it reached `argc`.
49 //
50 // This difference will only matter in very specific circumstances
51 // where `argc`/`argv` have been modified, but in unexpected ways,
52 // so it likely doesn't really matter which option we choose.
53 // See the following PR for further discussion:
54 // <https://github.com/rust-lang/rust/pull/125225>
55 break;
56 }
57
58 // SAFETY: Just checked that the pointer is not NULL, and arguments
59 // are otherwise guaranteed to be valid C strings.
60 let cstr = unsafe { CStr::from_ptr(ptr) };
61 vec.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
62 }
63
64 Args::new(vec)
65}
66
67#[cfg(any(
68 target_os = "linux",
69 target_os = "android",
70 target_os = "freebsd",
71 target_os = "dragonfly",
72 target_os = "netbsd",
73 target_os = "openbsd",
74 target_os = "cygwin",
75 target_os = "solaris",
76 target_os = "illumos",
77 target_os = "emscripten",
78 target_os = "haiku",
79 target_os = "hermit",
80 target_os = "l4re",
81 target_os = "fuchsia",
82 target_os = "redox",
83 target_os = "vxworks",
84 target_os = "horizon",
85 target_os = "aix",
86 target_os = "nto",
87 target_os = "hurd",
88 target_os = "rtems",
89 target_os = "nuttx",
90))]
91mod imp {
92 use crate::ffi::c_char;
93 use crate::ptr;
94 use crate::sync::atomic::{AtomicIsize, AtomicPtr, Ordering};
95
96 // The system-provided argc and argv, which we store in static memory
97 // here so that we can defer the work of parsing them until its actually
98 // needed.
99 //
100 // Note that we never mutate argv/argc, the argv array, or the argv
101 // strings, which allows the code in this file to be very simple.
102 static ARGC: AtomicIsize = AtomicIsize::new(0);
103 static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(ptr::null_mut());
104
105 unsafe fn really_init(argc: isize, argv: *const *const u8) {
106 // These don't need to be ordered with each other or other stores,
107 // because they only hold the unmodified system-provided argv/argc.
108 ARGC.store(argc, Ordering::Relaxed);
109 ARGV.store(argv as *mut _, Ordering::Relaxed);
110 }
111
112 #[inline(always)]
113 pub unsafe fn init(argc: isize, argv: *const *const u8) {
114 // on GNU/Linux if we are main then we will init argv and argc twice, it "duplicates work"
115 // BUT edge-cases are real: only using .init_array can break most emulators, dlopen, etc.
116 unsafe { really_init(argc, argv) };
117 }
118
119 /// glibc passes argc, argv, and envp to functions in .init_array, as a non-standard extension.
120 /// This allows `std::env::args` to work even in a `cdylib`, as it does on macOS and Windows.
121 #[cfg(all(target_os = "linux", target_env = "gnu"))]
122 #[used]
123 #[unsafe(link_section = ".init_array.00099")]
124 static ARGV_INIT_ARRAY: extern "C" fn(
125 crate::os::raw::c_int,
126 *const *const u8,
127 *const *const u8,
128 ) = {
129 extern "C" fn init_wrapper(
130 argc: crate::os::raw::c_int,
131 argv: *const *const u8,
132 _envp: *const *const u8,
133 ) {
134 unsafe { really_init(argc as isize, argv) };
135 }
136 init_wrapper
137 };
138
139 pub fn argc_argv() -> (isize, *const *const c_char) {
140 // Load ARGC and ARGV, which hold the unmodified system-provided
141 // argc/argv, so we can read the pointed-to memory without atomics or
142 // synchronization.
143 //
144 // If either ARGC or ARGV is still zero or null, then either there
145 // really are no arguments, or someone is asking for `args()` before
146 // initialization has completed, and we return an empty list.
147 let argv = ARGV.load(Ordering::Relaxed);
148 let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
149
150 // Cast from `*mut *const u8` to `*const *const c_char`
151 (argc, argv.cast())
152 }
153}
154
155// Use `_NSGetArgc` and `_NSGetArgv` on Apple platforms.
156//
157// Even though these have underscores in their names, they've been available
158// since the first versions of both macOS and iOS, and are declared in
159// the header `crt_externs.h`.
160//
161// NOTE: This header was added to the iOS 13.0 SDK, which has been the source
162// of a great deal of confusion in the past about the availability of these
163// APIs.
164//
165// NOTE(madsmtm): This has not strictly been verified to not cause App Store
166// rejections; if this is found to be the case, the previous implementation
167// of this used `[[NSProcessInfo processInfo] arguments]`.
168#[cfg(target_vendor = "apple")]
169mod imp {
170 use crate::ffi::{c_char, c_int};
171
172 pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
173 // No need to initialize anything in here, `libdyld.dylib` has already
174 // done the work for us.
175 }
176
177 pub fn argc_argv() -> (isize, *const *const c_char) {
178 unsafe extern "C" {
179 // These functions are in crt_externs.h.
180 fn _NSGetArgc() -> *mut c_int;
181 fn _NSGetArgv() -> *mut *mut *mut c_char;
182 }
183
184 // SAFETY: The returned pointer points to a static initialized early
185 // in the program lifetime by `libdyld.dylib`, and as such is always
186 // valid.
187 //
188 // NOTE: Similar to `_NSGetEnviron`, there technically isn't anything
189 // protecting us against concurrent modifications to this, and there
190 // doesn't exist a lock that we can take. Instead, it is generally
191 // expected that it's only modified in `main` / before other code
192 // runs, so reading this here should be fine.
193 let argc = unsafe { _NSGetArgc().read() };
194 // SAFETY: Same as above.
195 let argv = unsafe { _NSGetArgv().read() };
196
197 // Cast from `*mut *mut c_char` to `*const *const c_char`
198 (argc as isize, argv.cast())
199 }
200}