#![allow(
clippy::type_complexity,
clippy::option_option,
)]
#[cfg(unix)]
use libc::c_char;
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::io::Read;
#[cfg(unix)]
use std::{ffi::CStr, os::unix::ffi::OsStringExt};
use std::{ffi::OsString, fs, io, path, sync};
pub fn exe() -> io::Result<fs::File> {
exe_path().and_then(fs::File::open)
}
pub fn exe_path() -> io::Result<path::PathBuf> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
Ok(path::PathBuf::from("/proc/self/exe"))
}
#[cfg(any(target_os = "dragonfly"))]
{
Ok(path::PathBuf::from("/proc/curproc/file"))
}
#[cfg(any(target_os = "netbsd"))]
{
Ok(path::PathBuf::from("/proc/curproc/exe"))
}
#[cfg(any(target_os = "solaris"))]
{
Ok(path::PathBuf::from(format!(
"/proc/{}/path/a.out",
nix::unistd::getpid()
))) }
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "linux",
target_os = "netbsd",
target_os = "solaris"
)))]
{
std::env::current_exe()
}
}
lazy_static::lazy_static! {
static ref ARGV: sync::RwLock<Option<Option<Vec<OsString>>>> = sync::RwLock::new(None);
static ref ENVP: sync::RwLock<Option<Option<Vec<(OsString, OsString)>>>> =
sync::RwLock::new(None);
}
pub fn args() -> Option<Vec<String>> {
args_os().map(|x| x.into_iter().map(|a| a.into_string().unwrap()).collect())
}
pub fn vars() -> Option<Vec<(String, String)>> {
vars_os().map(|x| {
x.into_iter()
.map(|(a, b)| (a.into_string().unwrap(), b.into_string().unwrap()))
.collect()
})
}
pub fn args_os() -> Option<Vec<OsString>> {
if ARGV.read().unwrap().is_none() {
let mut write = ARGV.write().unwrap();
if write.is_none() {
*write = Some(argv_from_global().ok().or_else(|| argv_from_proc().ok()));
}
}
ARGV.read().unwrap().as_ref().unwrap().clone()
}
pub fn vars_os() -> Option<Vec<(OsString, OsString)>> {
if ENVP.read().unwrap().is_none() {
let mut write = ENVP.write().unwrap();
if write.is_none() {
*write = Some(envp_from_global().ok().or_else(|| envp_from_proc().ok()));
}
}
ENVP.read().unwrap().as_ref().unwrap().clone()
}
fn argv_from_global() -> Result<Vec<OsString>, ()> {
#[cfg(any(windows, target_os = "macos", target_os = "ios"))]
{
Ok(std::env::args_os().collect())
}
#[cfg(not(any(windows, target_os = "macos", target_os = "ios")))]
{
Err(())
}
}
fn argv_from_proc() -> Result<Vec<OsString>, io::Error> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
let mut cmdline = Vec::new();
let _ = fs::File::open("/proc/self/cmdline")?
.read_to_end(&mut cmdline)
.unwrap(); if let Some(b'\0') = cmdline.last() {
let null = cmdline.pop().unwrap();
assert_eq!(null, b'\0');
}
Ok(cmdline
.split(|&x| x == b'\0')
.map(|x| OsStringExt::from_vec(x.to_vec()))
.collect::<Vec<_>>())
}
#[cfg(not(any(target_os = "android", target_os = "linux")))]
{
Err(io::Error::new(
io::ErrorKind::NotFound,
"no /proc/self/cmdline equivalent",
))
}
}
fn envp_from_global() -> Result<Vec<(OsString, OsString)>, ()> {
#[cfg(unix)]
{
unsafe fn environ() -> *mut *const *const c_char {
#[cfg(target_os = "macos")]
{
extern "C" {
fn _NSGetEnviron() -> *mut *const *const c_char;
}
_NSGetEnviron()
}
#[cfg(not(target_os = "macos"))]
{
extern "C" {
static mut environ: *const *const c_char;
}
&mut environ
}
}
unsafe {
let mut environ = *environ();
if environ.is_null() {
return Err(());
}
let mut result = Vec::new();
while !(*environ).is_null() {
if let Some(key_value) = parse_env(CStr::from_ptr(*environ).to_bytes()) {
result.push(key_value);
}
environ = environ.offset(1);
}
Ok(result)
}
}
#[cfg(windows)]
{
Ok(std::env::vars_os().collect())
}
}
fn envp_from_proc() -> Result<Vec<(OsString, OsString)>, io::Error> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
let mut envp = Vec::new();
let _ = fs::File::open("/proc/self/environ")?
.read_to_end(&mut envp)
.unwrap(); if let Some(b'\0') = envp.last() {
let null = envp.pop().unwrap();
assert_eq!(null, b'\0');
}
Ok(envp
.split(|&x| x == b'\0')
.flat_map(|x| parse_env(x))
.collect::<Vec<_>>())
}
#[cfg(not(any(target_os = "android", target_os = "linux")))]
{
Err(io::Error::new(
io::ErrorKind::NotFound,
"no /proc/self/environ equivalent",
))
}
}
#[cfg(unix)]
fn parse_env(input: &[u8]) -> Option<(OsString, OsString)> {
if input.is_empty() {
return None;
}
let pos = input[1..].iter().position(|&x| x == b'=').map(|p| p + 1);
pos.map(|p| {
(
OsStringExt::from_vec(input[..p].to_vec()),
OsStringExt::from_vec(input[p + 1..].to_vec()),
)
})
}
#[doc(hidden)]
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos"))]
#[cfg_attr(
all(target_os = "linux", target_env = "gnu"),
link_section = ".init_array"
)]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[used]
pub static GRAB_ARGV_ENVP: extern "C" fn(
argc: libc::c_int,
argv: *const *const c_char,
envp: *const *const c_char,
) = {
#[cfg_attr(target_os = "linux", link_section = ".text.startup")]
extern "C" fn grab_argv_envp(
_argc: libc::c_int, argv: *const *const c_char, envp: *const *const c_char,
) {
unsafe {
let mut collect_args: Vec<OsString> = Vec::new();
let mut argv = argv;
while !(*argv).is_null() {
collect_args.push(OsStringExt::from_vec(
CStr::from_ptr(*argv).to_bytes().to_vec(),
));
argv = argv.offset(1);
}
let mut collect_vars: Vec<(OsString, OsString)> = Vec::new();
let mut envp = envp;
while !(*envp).is_null() {
let x = CStr::from_ptr(*envp).to_bytes();
if let Some(x) = parse_env(x) {
collect_vars.push(x);
}
envp = envp.offset(1);
}
*ARGV.write().unwrap() = Some(Some(collect_args));
*ENVP.write().unwrap() = Some(Some(collect_vars));
}
}
grab_argv_envp
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same() {
let args = vec![
ARGV.read().unwrap().clone().unwrap_or(None),
argv_from_global().ok(),
argv_from_proc().ok(),
Some(std::env::args_os().collect::<Vec<_>>()),
];
let mut args2 = args.clone().into_iter().flatten().collect::<Vec<_>>();
args2.dedup();
assert!(args2.len() == 1, "{:?}", args);
let args = vec![
ENVP.read().unwrap().clone().unwrap_or(None),
envp_from_global().ok(),
envp_from_proc().ok(),
Some(std::env::vars_os().collect::<Vec<_>>()),
];
let mut args2 = args.clone().into_iter().flatten().collect::<Vec<_>>();
args2.dedup();
assert!(args2.len() == 1, "{:?}", args);
}
}