get_env/
lib.rs

1//! Get argv and envp by hook or by crook.
2//!
3//! **[Crates.io](https://crates.io/crates/get_env) │ [Repo](https://github.com/alecmocatta/get_env)**
4//!
5//! This library goes further than the stdlib to get arguments and environment
6//! variables, including reading from `/proc/self/cmdline` and similar.
7//!
8//! This is helpful for library crates that don't want to require them to be
9//! passed down to the library by the user; particularly if called from a
10//! non-Rust application where the Rust stdlib hasn't had a chance to capture
11//! them from the `int main (int argc, char *argv[])` invocation thus breaking
12//! `std::env::args()`.
13//!
14//! # Example
15//!
16//! ```
17//! extern crate get_env;
18//!
19//! pub fn my_library_func() {
20//! 	let args = get_env::args();
21//! 	let vars = get_env::vars();
22//! }
23//! ```
24//!
25//! # Note
26//!
27//! This currently requires Rust nightly for the `used` feature.
28
29#![doc(html_root_url = "https://docs.rs/get_env/0.1.0")]
30#![cfg_attr(
31	any(
32		all(target_os = "linux", target_env = "gnu"),
33		target_os = "macos"
34	),
35	feature(used)
36)]
37#![warn(
38	missing_copy_implementations,
39	missing_debug_implementations,
40	missing_docs,
41	trivial_numeric_casts,
42	unused_extern_crates,
43	unused_import_braces,
44	unused_qualifications,
45	unused_results,
46)] // from https://github.com/rust-unofficial/patterns/blob/master/anti_patterns/deny-warnings.md
47#![cfg_attr(feature = "cargo-clippy", warn(clippy_pedantic))]
48#![cfg_attr(
49	feature = "cargo-clippy",
50	allow(type_complexity, option_option, indexing_slicing)
51)]
52
53#[macro_use]
54extern crate lazy_static;
55#[cfg(target_family = "unix")]
56extern crate libc;
57
58#[cfg(target_family = "unix")]
59use libc::c_char;
60#[cfg(
61	any(
62		target_family = "windows",
63		target_os = "macos",
64		target_os = "ios"
65	)
66)]
67use std::env;
68#[cfg(target_family = "unix")]
69use std::{ffi::CStr, os::unix::ffi::OsStringExt};
70use std::{ffi::OsString, io, sync};
71#[cfg(any(target_os = "android", target_os = "linux"))]
72use std::{fs, io::Read};
73
74lazy_static! {
75	static ref ARGV: sync::RwLock<Option<Option<Vec<OsString>>>> = sync::RwLock::new(None);
76	static ref ENVP: sync::RwLock<Option<Option<Vec<(OsString, OsString)>>>> =
77		sync::RwLock::new(None);
78}
79
80/// Returns the arguments which this program was started with (normally passed
81/// via the command line).
82///
83/// The first element is traditionally the path of the executable, but it can be
84/// set to arbitrary text, and it may not even exist, so this property should
85/// not be relied upon for security purposes.
86///
87/// # Errors
88///
89/// This will return `None` if `get_env` can't acquire them. Please file an issue.
90///
91/// # Panics
92///
93/// Will panic if any argument to the process is not valid unicode. If this is
94/// not desired, consider using the [`get_env::args_os`] function.
95///
96/// [`get_env::args_os`]: fn.args_os.html
97///
98/// # Examples
99///
100/// ```
101/// extern crate get_env;
102///
103/// // Prints each argument on a separate line
104/// for argument in get_env::args().expect("Couldn't retrieve arguments") {
105///     println!("{}", argument);
106/// }
107/// ```
108pub fn args() -> Option<Vec<String>> {
109	args_os().map(|x| x.into_iter().map(|a| a.into_string().unwrap()).collect())
110}
111
112/// Returns a vector of (variable, value) pairs of strings, for all the
113/// environment variables of the current process.
114///
115/// # Errors
116///
117/// This will return `None` if `get_env` can't acquire them. Please file an issue.
118///
119/// # Panics
120///
121/// Will panic if any key or value in the environment is not valid unicode. If
122/// this is not desired, consider using the [`get_env::vars_os`] function.
123///
124/// [`get_env::vars_os`]: fn.vars_os.html
125///
126/// # Examples
127///
128/// ```
129/// extern crate get_env;
130///
131/// // Prints the environment variables
132/// for (key, value) in get_env::vars().expect("Couldn't retrieve env vars") {
133///     println!("{}: {}", key, value);
134/// }
135/// ```
136pub fn vars() -> Option<Vec<(String, String)>> {
137	vars_os().map(|x| {
138		x.into_iter()
139			.map(|(a, b)| (a.into_string().unwrap(), b.into_string().unwrap()))
140			.collect()
141	})
142}
143
144/// Returns the arguments which this program was started with (normally passed
145/// via the command line).
146///
147/// The first element is traditionally the path of the executable, but it can be
148/// set to arbitrary text, and it may not even exist, so this property should
149/// not be relied upon for security purposes.
150///
151/// # Errors
152///
153/// This will return `None` if `get_env` can't acquire them. Please file an issue.
154///
155/// # Examples
156///
157/// ```
158/// extern crate get_env;
159///
160/// // Prints each argument on a separate line
161/// for argument in get_env::args_os().expect("Couldn't retrieve arguments") {
162///     println!("{:?}", argument);
163/// }
164/// ```
165pub fn args_os() -> Option<Vec<OsString>> {
166	if ARGV.read().unwrap().is_none() {
167		let mut write = ARGV.write().unwrap();
168		if write.is_none() {
169			*write = Some(argv_from_global().ok().or_else(|| argv_from_proc().ok()));
170		}
171	}
172	ARGV.read().unwrap().as_ref().unwrap().clone()
173}
174
175/// Returns a vector of (variable, value) pairs of OS strings, for all the
176/// environment variables of the current process.
177///
178/// # Errors
179///
180/// This will return `None` if `get_env` can't acquire them. Please file an issue.
181///
182/// # Examples
183///
184/// ```
185/// extern crate get_env;
186///
187/// // Prints the environment variables
188/// for (key, value) in get_env::vars_os().expect("Couldn't retrieve env vars") {
189///     println!("{:?}: {:?}", key, value);
190/// }
191/// ```
192pub fn vars_os() -> Option<Vec<(OsString, OsString)>> {
193	if ENVP.read().unwrap().is_none() {
194		let mut write = ENVP.write().unwrap();
195		if write.is_none() {
196			*write = Some(envp_from_global().ok().or_else(|| envp_from_proc().ok()));
197		}
198	}
199	ENVP.read().unwrap().as_ref().unwrap().clone()
200}
201
202fn argv_from_global() -> Result<Vec<OsString>, ()> {
203	#[cfg(
204		any(
205			target_family = "windows",
206			target_os = "macos",
207			target_os = "ios"
208		)
209	)]
210	{
211		// std uses windows GetCommandLineW and mac _NSGetArgv
212		Ok(env::args_os().collect())
213	}
214	#[cfg(
215		not(
216			any(
217				target_family = "windows",
218				target_os = "macos",
219				target_os = "ios"
220			)
221		)
222	)]
223	{
224		Err(())
225	}
226}
227
228fn argv_from_proc() -> Result<Vec<OsString>, io::Error> {
229	#[cfg(any(target_os = "android", target_os = "linux"))]
230	{
231		let mut cmdline = Vec::new();
232		let _ = fs::File::open("/proc/self/cmdline")?
233			.read_to_end(&mut cmdline)
234			.unwrap(); // limited to 4096 bytes?
235		if let Some(b'\0') = cmdline.last() {
236			let null = cmdline.pop().unwrap();
237			assert_eq!(null, b'\0');
238		}
239		Ok(cmdline
240			.split(|&x| x == b'\0')
241			.map(|x| OsStringExt::from_vec(x.to_vec()))
242			.collect::<Vec<_>>())
243	}
244	#[cfg(not(any(target_os = "android", target_os = "linux")))]
245	{
246		Err(io::Error::new(
247			io::ErrorKind::NotFound,
248			"no /proc/self/cmdline equivalent",
249		))
250	}
251}
252
253fn envp_from_global() -> Result<Vec<(OsString, OsString)>, ()> {
254	#[cfg(target_family = "unix")]
255	{
256		unsafe fn environ() -> *mut *const *const c_char {
257			#[cfg(target_os = "macos")]
258			{
259				extern "C" {
260					fn _NSGetEnviron() -> *mut *const *const c_char;
261				}
262				_NSGetEnviron()
263			}
264			#[cfg(not(target_os = "macos"))]
265			{
266				extern "C" {
267					// #[cfg_attr(target_os = "linux", linkage = "extern_weak")]
268					static mut environ: *const *const c_char;
269				}
270				&mut environ
271			}
272		}
273		unsafe {
274			let mut environ = *environ();
275			if environ.is_null() {
276				return Err(());
277			}
278			let mut result = Vec::new();
279			while !(*environ).is_null() {
280				if let Some(key_value) = parse_env(CStr::from_ptr(*environ).to_bytes()) {
281					result.push(key_value);
282				}
283				environ = environ.offset(1);
284			}
285			Ok(result)
286		}
287	}
288	#[cfg(target_family = "windows")]
289	{
290		// std uses GetEnvironmentStringsW
291		Ok(env::vars_os().collect())
292	}
293}
294fn envp_from_proc() -> Result<Vec<(OsString, OsString)>, io::Error> {
295	#[cfg(any(target_os = "android", target_os = "linux"))]
296	{
297		let mut envp = Vec::new();
298		let _ = fs::File::open("/proc/self/environ")?
299			.read_to_end(&mut envp)
300			.unwrap(); // limited to 4096 bytes?
301		if let Some(b'\0') = envp.last() {
302			let null = envp.pop().unwrap();
303			assert_eq!(null, b'\0');
304		}
305		Ok(envp
306			.split(|&x| x == b'\0')
307			.flat_map(|x| parse_env(x))
308			.collect::<Vec<_>>())
309	}
310	#[cfg(not(any(target_os = "android", target_os = "linux")))]
311	{
312		Err(io::Error::new(
313			io::ErrorKind::NotFound,
314			"no /proc/self/environ equivalent",
315		))
316	}
317}
318
319#[cfg(target_family = "unix")]
320fn parse_env(input: &[u8]) -> Option<(OsString, OsString)> {
321	// https://github.com/rust-lang/rust/blob/a1e6bcb2085cba3d5e549ba3b175f99487c21639/src/libstd/sys/unix/os.rs#L431
322	if input.is_empty() {
323		return None;
324	}
325	let pos = input[1..].iter().position(|&x| x == b'=').map(|p| p + 1);
326	pos.map(|p| {
327		(
328			OsStringExt::from_vec(input[..p].to_vec()),
329			OsStringExt::from_vec(input[p + 1..].to_vec()),
330		)
331	})
332}
333
334#[doc(hidden)]
335// https://github.com/golang/go/issues/13492
336#[cfg(
337	any(
338		all(target_os = "linux", target_env = "gnu"),
339		target_os = "macos"
340	)
341)]
342#[cfg_attr(
343	all(target_os = "linux", target_env = "gnu"),
344	link_section = ".init_array"
345)]
346#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
347// #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] XIU
348#[used] // no_mangle and deny(private_no_mangle_statics), or doing something with addr elsewhere might be alternative?
349pub static GRAB_ARGV_ENVP: extern "C" fn(
350	argc: libc::c_int,
351	argv: *const *const c_char,
352	envp: *const *const c_char,
353) = {
354	// Or should it be an array? https://github.com/rust-lang/rust/pull/39987#issue-107077124
355	extern "C" fn grab_argv_envp(
356		_argc: libc::c_int, argv: *const *const c_char, envp: *const *const c_char,
357	) {
358		unsafe {
359			let mut collect_args: Vec<OsString> = Vec::new();
360			let mut argv = argv;
361			while !(*argv).is_null() {
362				collect_args.push(OsStringExt::from_vec(
363					CStr::from_ptr(*argv).to_bytes().to_vec(),
364				));
365				argv = argv.offset(1);
366			}
367			let mut collect_vars: Vec<(OsString, OsString)> = Vec::new();
368			let mut envp = envp;
369			while !(*envp).is_null() {
370				let x = CStr::from_ptr(*envp).to_bytes();
371				if let Some(x) = parse_env(x) {
372					collect_vars.push(x);
373				}
374				envp = envp.offset(1);
375			}
376			*ARGV.write().unwrap() = Some(Some(collect_args));
377			*ENVP.write().unwrap() = Some(Some(collect_vars));
378		}
379	}
380	grab_argv_envp
381};
382
383#[cfg(test)]
384mod tests {
385	use super::*;
386	use std::env;
387	#[test]
388	fn same() {
389		let args = vec![
390			ARGV.read().unwrap().clone().unwrap_or(None),
391			argv_from_global().ok(),
392			argv_from_proc().ok(),
393			Some(env::args_os().collect::<Vec<_>>()),
394		];
395		let mut args2 = args.clone().into_iter().flat_map(|x| x).collect::<Vec<_>>();
396		args2.dedup();
397		assert!(args2.len() == 1, "{:?}", args);
398
399		let args = vec![
400			ENVP.read().unwrap().clone().unwrap_or(None),
401			envp_from_global().ok(),
402			envp_from_proc().ok(),
403			Some(env::vars_os().collect::<Vec<_>>()),
404		];
405		let mut args2 = args.clone().into_iter().flat_map(|x| x).collect::<Vec<_>>();
406		args2.dedup();
407		assert!(args2.len() == 1, "{:?}", args);
408	}
409}