Files
RedBear-OS/src/header/getopt/mod.rs
T
Peter Limkilde Svendsen ce26bdc8b6 Improve getopt.h docs
2026-04-01 23:43:32 +02:00

245 lines
8.3 KiB
Rust

//! `getopt.h` implementation.
//!
//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
use crate::{
header::{
stdio, string,
unistd::{optarg, opterr, optind, optopt},
},
platform::types::{c_char, c_int, size_t},
};
use core::ptr;
static mut CURRENT_OPT: *mut c_char = ptr::null_mut();
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
pub const no_argument: c_int = 0;
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
pub const required_argument: c_int = 1;
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
pub const optional_argument: c_int = 2;
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
#[repr(C)]
pub struct option {
name: *const c_char,
has_arg: c_int,
flag: *mut c_int,
val: c_int,
}
/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
///
/// Functions the same as `getopt` but also accepts long options.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn getopt_long(
argc: c_int,
argv: *const *mut c_char,
optstring: *const c_char,
longopts: *const option,
longindex: *mut c_int,
) -> c_int {
// if optarg is not set, we still don't want the previous value leaking
unsafe {
optarg = ptr::null_mut();
}
// handle reinitialization request
unsafe {
if optind == 0 {
optind = 1;
CURRENT_OPT = ptr::null_mut();
}
}
if unsafe { CURRENT_OPT.is_null() || *CURRENT_OPT == 0 } {
if unsafe { optind >= argc } {
-1
} else {
let current_arg = unsafe { *argv.offset(optind as isize) };
if unsafe {
current_arg.is_null()
|| *current_arg != b'-' as c_char
|| *current_arg.offset(1) == 0
} {
-1
} else if unsafe { string::strcmp(current_arg, c"--".as_ptr()) == 0 } {
unsafe {
optind += 1;
}
-1
} else {
// remove the '-'
let current_arg = unsafe { current_arg.offset(1) };
if unsafe { *current_arg == b'-' as c_char } && !longopts.is_null() {
let current_arg = unsafe { current_arg.offset(1) };
// is a long option
for i in 0.. {
let opt = unsafe { &*longopts.offset(i) };
if opt.name.is_null() {
break;
}
let mut end = 0;
while {
let c = unsafe { *current_arg.offset(end) };
c != 0 && c != b'=' as c_char
} {
end += 1;
}
if unsafe { string::strncmp(current_arg, opt.name, end as size_t) == 0 } {
unsafe {
optind += 1;
if !longindex.is_null() {
*longindex = i as c_int;
}
}
if opt.has_arg == optional_argument {
unsafe {
if *current_arg.offset(end) == b'=' as c_char {
optarg = current_arg.offset(end + 1);
}
}
} else if opt.has_arg == required_argument {
unsafe {
if *current_arg.offset(end) == b'=' as c_char {
optarg = current_arg.offset(end + 1);
} else if optind < argc {
optarg = *argv.offset(optind as isize);
optind += 1;
} else if *optstring == b':' as c_char {
return c_int::from(b':');
} else {
stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr);
stdio::fputs(
c": option '--".as_ptr().cast(),
&raw mut *stdio::stderr,
);
stdio::fputs(current_arg, &raw mut *stdio::stderr);
stdio::fputs(
c"' requires an argument\n".as_ptr().cast(),
&raw mut *stdio::stderr,
);
return c_int::from(b'?');
}
}
}
if opt.flag.is_null() {
return opt.val;
} else {
unsafe { *opt.flag = opt.val };
return 0;
}
}
}
}
unsafe { parse_arg(argc, argv, current_arg, optstring) }
}
}
} else {
unsafe { parse_arg(argc, argv, CURRENT_OPT, optstring) }
}
}
unsafe fn parse_arg(
argc: c_int,
argv: *const *mut c_char,
current_arg: *mut c_char,
optstring: *const c_char,
) -> c_int {
let update_current_opt = || unsafe {
CURRENT_OPT = current_arg.offset(1);
if *CURRENT_OPT == 0 {
optind += 1;
}
};
let print_error = |desc: &[u8]| unsafe {
// NOTE: we don't use fprintf to get around the usage of va_list
stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr);
stdio::fputs(desc.as_ptr().cast(), &raw mut *stdio::stderr);
stdio::fputc((*current_arg).into(), &raw mut *stdio::stderr);
stdio::fputc(b'\n'.into(), &raw mut *stdio::stderr);
};
match unsafe { find_option(*current_arg, optstring) } {
Some(GetoptOption::Flag) => {
update_current_opt();
unsafe { c_int::from(*current_arg) }
}
Some(GetoptOption::OptArg) => unsafe {
CURRENT_OPT = c"".as_ptr().cast_mut();
if *current_arg.offset(1) == 0 {
optind += 2;
if optind > argc {
CURRENT_OPT = ptr::null_mut();
optopt = c_int::from(*current_arg);
let errch = if *optstring == b':' as c_char {
b':'
} else {
if opterr != 0 {
print_error(b": option requries an argument -- \0");
}
b'?'
};
c_int::from(errch)
} else {
optarg = *argv.offset(optind as isize - 1);
c_int::from(*current_arg)
}
} else {
optarg = current_arg.offset(1);
optind += 1;
c_int::from(*current_arg)
}
},
None => {
// couldn't find the given option in optstring
if unsafe { opterr != 0 } {
print_error(b": illegal option -- \0");
}
update_current_opt();
unsafe {
optopt = c_int::from(*current_arg);
}
c_int::from(b'?')
}
}
}
enum GetoptOption {
Flag,
OptArg,
}
unsafe fn find_option(ch: c_char, optstring: *const c_char) -> Option<GetoptOption> {
let mut i = 0;
while unsafe { *optstring.offset(i) != 0 } {
if unsafe { *optstring.offset(i) == ch } {
let result = if unsafe { *optstring.offset(i + 1) == b':' as c_char } {
GetoptOption::OptArg
} else {
GetoptOption::Flag
};
return Some(result);
}
i += 1;
}
None
}