Skip to main content

relibc/header/getopt/
mod.rs

1//! `getopt.h` implementation.
2//!
3//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
4
5use crate::{
6    byte_literal::ByteLiteral,
7    header::{
8        stdio, string,
9        unistd::{optarg, opterr, optind, optopt},
10    },
11    platform::types::{c_char, c_int, size_t},
12};
13use core::ptr;
14
15/// cbindgen:ignore
16static mut CURRENT_OPT: *mut c_char = ptr::null_mut();
17
18/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
19pub const no_argument: c_int = 0;
20/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
21pub const required_argument: c_int = 1;
22/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
23pub const optional_argument: c_int = 2;
24
25/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
26#[repr(C)]
27pub struct option {
28    name: *const c_char,
29    has_arg: c_int,
30    flag: *mut c_int,
31    val: c_int,
32}
33
34/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getopt.3.html>.
35///
36/// Functions the same as `getopt` but also accepts long options.
37#[unsafe(no_mangle)]
38pub unsafe extern "C" fn getopt_long(
39    argc: c_int,
40    argv: *const *mut c_char,
41    optstring: *const c_char,
42    longopts: *const option,
43    longindex: *mut c_int,
44) -> c_int {
45    // if optarg is not set, we still don't want the previous value leaking
46    unsafe {
47        optarg = ptr::null_mut();
48    }
49
50    // handle reinitialization request
51    unsafe {
52        if optind == 0 {
53            optind = 1;
54            CURRENT_OPT = ptr::null_mut();
55        }
56    }
57
58    if unsafe { CURRENT_OPT.is_null() || *CURRENT_OPT == 0 } {
59        if unsafe { optind >= argc } {
60            -1
61        } else {
62            let current_arg = unsafe { *argv.offset(optind as isize) };
63            if unsafe {
64                current_arg.is_null()
65                    || *current_arg != ByteLiteral::cast_cchar(b'-')
66                    || *current_arg.offset(1) == 0
67            } {
68                -1
69            } else if unsafe { string::strcmp(current_arg, c"--".as_ptr()) == 0 } {
70                unsafe {
71                    optind += 1;
72                }
73                -1
74            } else {
75                // remove the '-'
76                let current_arg = unsafe { current_arg.offset(1) };
77
78                if unsafe { *current_arg == ByteLiteral::cast_cchar(b'-') } && !longopts.is_null() {
79                    let current_arg = unsafe { current_arg.offset(1) };
80                    // is a long option
81                    for i in 0.. {
82                        let opt = unsafe { &*longopts.offset(i) };
83                        if opt.name.is_null() {
84                            break;
85                        }
86
87                        let mut end = 0;
88                        while {
89                            let c = unsafe { *current_arg.offset(end) };
90                            c != 0 && c != ByteLiteral::cast_cchar(b'=')
91                        } {
92                            end += 1;
93                        }
94
95                        if unsafe { string::strncmp(current_arg, opt.name, end as size_t) == 0 } {
96                            unsafe {
97                                optind += 1;
98                                if !longindex.is_null() {
99                                    *longindex = i as c_int;
100                                }
101                            }
102
103                            if opt.has_arg == optional_argument {
104                                unsafe {
105                                    if *current_arg.offset(end) == ByteLiteral::cast_cchar(b'=') {
106                                        optarg = current_arg.offset(end + 1);
107                                    }
108                                }
109                            } else if opt.has_arg == required_argument {
110                                unsafe {
111                                    if *current_arg.offset(end) == ByteLiteral::cast_cchar(b'=') {
112                                        optarg = current_arg.offset(end + 1);
113                                    } else if optind < argc {
114                                        optarg = *argv.offset(optind as isize);
115                                        optind += 1;
116                                    } else if *optstring == ByteLiteral::cast_cchar(b':') {
117                                        return c_int::from(b':');
118                                    } else {
119                                        stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr);
120                                        stdio::fputs(
121                                            c": option '--".as_ptr().cast(),
122                                            &raw mut *stdio::stderr,
123                                        );
124                                        stdio::fputs(current_arg, &raw mut *stdio::stderr);
125                                        stdio::fputs(
126                                            c"' requires an argument\n".as_ptr().cast(),
127                                            &raw mut *stdio::stderr,
128                                        );
129                                        return c_int::from(b'?');
130                                    }
131                                }
132                            }
133
134                            if opt.flag.is_null() {
135                                return opt.val;
136                            } else {
137                                unsafe { *opt.flag = opt.val };
138                                return 0;
139                            }
140                        }
141                    }
142                }
143
144                unsafe { parse_arg(argc, argv, current_arg, optstring) }
145            }
146        }
147    } else {
148        unsafe { parse_arg(argc, argv, CURRENT_OPT, optstring) }
149    }
150}
151
152unsafe fn parse_arg(
153    argc: c_int,
154    argv: *const *mut c_char,
155    current_arg: *mut c_char,
156    optstring: *const c_char,
157) -> c_int {
158    let update_current_opt = || unsafe {
159        CURRENT_OPT = current_arg.offset(1);
160        if *CURRENT_OPT == 0 {
161            optind += 1;
162        }
163    };
164
165    let print_error = |desc: &[u8]| unsafe {
166        // NOTE: we don't use fprintf to get around the usage of va_list
167        stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr);
168        stdio::fputs(desc.as_ptr().cast(), &raw mut *stdio::stderr);
169        stdio::fputc((*current_arg).into(), &raw mut *stdio::stderr);
170        stdio::fputc(b'\n'.into(), &raw mut *stdio::stderr);
171    };
172
173    match unsafe { find_option(*current_arg, optstring) } {
174        Some(GetoptOption::Flag) => {
175            update_current_opt();
176
177            unsafe { c_int::from(*current_arg) }
178        }
179        Some(GetoptOption::OptArg) => unsafe {
180            CURRENT_OPT = c"".as_ptr().cast_mut();
181            if *current_arg.offset(1) == 0 {
182                optind += 2;
183                if optind > argc {
184                    CURRENT_OPT = ptr::null_mut();
185
186                    optopt = c_int::from(*current_arg);
187                    let errch = if *optstring == ByteLiteral::cast_cchar(b':') {
188                        b':'
189                    } else {
190                        if opterr != 0 {
191                            print_error(b": option requries an argument -- \0");
192                        }
193
194                        b'?'
195                    };
196                    c_int::from(errch)
197                } else {
198                    optarg = *argv.offset(optind as isize - 1);
199
200                    c_int::from(*current_arg)
201                }
202            } else {
203                optarg = current_arg.offset(1);
204                optind += 1;
205
206                c_int::from(*current_arg)
207            }
208        },
209        None => {
210            // couldn't find the given option in optstring
211            if unsafe { opterr != 0 } {
212                print_error(b": illegal option -- \0");
213            }
214
215            update_current_opt();
216
217            unsafe {
218                optopt = c_int::from(*current_arg);
219            }
220            c_int::from(b'?')
221        }
222    }
223}
224
225enum GetoptOption {
226    Flag,
227    OptArg,
228}
229
230unsafe fn find_option(ch: c_char, optstring: *const c_char) -> Option<GetoptOption> {
231    let mut i = 0;
232
233    while unsafe { *optstring.offset(i) != 0 } {
234        if unsafe { *optstring.offset(i) == ch } {
235            let result = if unsafe { *optstring.offset(i + 1) == ByteLiteral::cast_cchar(b':') } {
236                GetoptOption::OptArg
237            } else {
238                GetoptOption::Flag
239            };
240            return Some(result);
241        }
242        i += 1;
243    }
244
245    None
246}