#![doc = include_str!("../README.md")]
#![deny(missing_docs, clippy::undocumented_unsafe_blocks)]
use core::iter::FusedIterator;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::ffi::OsStr;
use std::os::raw::{c_char, c_int};
use std::os::unix::prelude::OsStrExt;
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
compile_error!("appleargs is not supported on this platform");
#[derive(Clone)]
pub struct AppleArgs {
inner: core::slice::Iter<'static, Vec<u8>>,
}
impl core::fmt::Debug for AppleArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list()
.entries(self.inner.clone().map(str_from_slice))
.finish()
}
}
impl Iterator for AppleArgs {
type Item = &'static str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(str_from_slice)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn count(self) -> usize {
self.inner.len()
}
}
impl ExactSizeIterator for AppleArgs {
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
impl DoubleEndedIterator for AppleArgs {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(str_from_slice)
}
}
impl FusedIterator for AppleArgs {}
#[inline]
pub fn apple_args() -> AppleArgs {
let inner = args_slice_iter();
AppleArgs { inner }
}
#[derive(Clone)]
pub struct AppleArgsOs {
inner: core::slice::Iter<'static, Vec<u8>>,
}
impl core::fmt::Debug for AppleArgsOs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list()
.entries(self.inner.clone().map(|v| OsStr::from_bytes(v)))
.finish()
}
}
impl Iterator for AppleArgsOs {
type Item = &'static OsStr;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|v| OsStr::from_bytes(v))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn count(self) -> usize {
self.inner.len()
}
}
impl ExactSizeIterator for AppleArgsOs {
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
impl DoubleEndedIterator for AppleArgsOs {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(|v| OsStr::from_bytes(v))
}
}
impl FusedIterator for AppleArgsOs {}
#[inline]
pub fn apple_args_os() -> AppleArgsOs {
let inner = args_slice_iter();
AppleArgsOs { inner }
}
#[allow(clippy::ptr_arg)]
fn str_from_slice(bytes: &Vec<u8>) -> &str {
core::str::from_utf8(bytes).expect("apple argument was not valid UTF-8")
}
fn args_slice_iter() -> core::slice::Iter<'static, Vec<u8>> {
let data = ARGS_DATA.load(Ordering::Acquire);
NonNull::new(data)
.map(|ptr| {
let len = ARGS_LEN.load(Ordering::Relaxed);
unsafe { core::slice::from_raw_parts(ptr.as_ptr(), len) }
})
.unwrap_or(&[])
.iter()
}
static ARGS_DATA: AtomicPtr<Vec<u8>> = AtomicPtr::new(ptr::null_mut());
static ARGS_LEN: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn init_function(
_argc: c_int,
_argv: *const *const c_char,
_envp: *const *const c_char,
mut applep: *const *const c_char,
) {
let mut v: Vec<Vec<u8>> = Vec::new();
while !applep.is_null() && !applep.read().is_null() {
let p: *const i8 = applep.read();
let len = strlen(p);
let ptr = p as *const u8;
let s = core::slice::from_raw_parts(ptr, len);
if !s.is_empty() {
v.push(s.to_owned());
}
applep = applep.add(1);
}
ARGS_LEN.store(v.len(), Ordering::Relaxed);
ARGS_DATA.store(
Box::into_raw(v.into_boxed_slice()).cast::<Vec<u8>>(),
Ordering::Release,
);
}
extern "C" {
fn strlen(s: *const c_char) -> usize;
}
#[used]
#[cfg_attr(
any(target_os = "macos", target_os = "ios"),
link_section = "__DATA,__mod_init_func"
)]
static CTOR: unsafe extern "C" fn(
argc: c_int,
argv: *const *const c_char,
envp: *const *const c_char,
applep: *const *const c_char,
) = init_function;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_check() {
let args = apple_args();
assert_ne!(args.clone().count(), 0);
for arg in args {
println!("Arg: {arg:?}");
}
let args = apple_args_os();
assert_ne!(!args.count(), 0);
}
}