Skip to main content

c/
getopt_impl.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// getopt / getopt_long / getopt_long_only (port of musl's implementation).
5
6#[repr(C)]
7#[derive(Copy, Clone)]
8pub struct LongOption {
9    pub name: *const c_char,
10    pub has_arg: c_int,
11    pub flag: *mut c_int,
12    pub val: c_int,
13}
14
15#[no_mangle]
16pub static mut __optpos: c_int = 0;
17#[no_mangle]
18pub static mut __optreset: c_int = 0;
19
20unsafe fn getopt_reset_if_needed() {
21    if optind == 0 || __optreset != 0 || optreset != 0 {
22        __optreset = 0;
23        optreset = 0;
24        __optpos = 0;
25        optind = 1;
26    }
27}
28
29unsafe fn opt_char_len(s: *const c_char) -> usize {
30    // Option characters are bytes; UTF-8 continuation bytes are part of the
31    // same option character for the purposes of matching.
32    let b = *s as u8;
33    if b < 0x80 { 1 }
34    else if b >= 0xf0 { 4 }
35    else if b >= 0xe0 { 3 }
36    else if b >= 0xc0 { 2 }
37    else { 1 }
38}
39
40#[no_mangle]
41pub unsafe extern "C" fn getopt(
42    argc: c_int,
43    argv: *const *const c_char,
44    optstring_in: *const c_char,
45) -> c_int {
46    getopt_reset_if_needed();
47    if argc <= 0 || argv.is_null() || optstring_in.is_null() { return -1; }
48    if optind >= argc { return -1; }
49    let av = argv.add(optind as usize);
50    if (*av).is_null() { return -1; }
51    let arg = *av;
52    if *arg != b'-' as c_char {
53        if *optstring_in == b'-' as c_char {
54            optarg = arg as *mut c_char;
55            optind += 1;
56            return 1;
57        }
58        return -1;
59    }
60    if *arg.add(1) == 0 { return -1; }
61    if *arg.add(1) == b'-' as c_char && *arg.add(2) == 0 {
62        optind += 1;
63        return -1;
64    }
65    if __optpos == 0 { __optpos = 1; }
66    let clen = opt_char_len(arg.add(__optpos as usize));
67    let c = *arg.add(__optpos as usize) as u8;
68    let optchar = arg.add(__optpos as usize);
69    __optpos += clen as c_int;
70    if *arg.add(__optpos as usize) == 0 {
71        optind += 1;
72        __optpos = 0;
73    }
74
75    let mut spec = optstring_in;
76    if *spec == b'-' as c_char || *spec == b'+' as c_char { spec = spec.add(1); }
77
78    let mut i = 0usize;
79    let mut found = false;
80    while *spec.add(i) != 0 {
81        if *spec.add(i) as u8 == c { found = true; break; }
82        i += 1;
83    }
84    if !found || c == b':' {
85        optopt = c as c_int;
86        if *spec != b':' as c_char && opterr != 0 {
87            let mut msg = [0 as c_char; 256];
88            let prog = if !(*argv).is_null() { *argv } else { b"\0".as_ptr() as *const c_char };
89            crate::fprintf(
90                crate::stderr,
91                b"%s: unrecognized option: %.*s\n\0".as_ptr() as *const c_char,
92                prog,
93                clen as c_int,
94                optchar,
95            );
96            let _ = &mut msg;
97        }
98        return b'?' as c_int;
99    }
100    if *spec.add(i + 1) == b':' as c_char {
101        optarg = core::ptr::null_mut();
102        if *spec.add(i + 2) != b':' as c_char || __optpos != 0 {
103            if optind >= argc {
104                optopt = c as c_int;
105                if *spec == b':' as c_char { return b':' as c_int; }
106                if opterr != 0 {
107                    let prog = if !(*argv).is_null() { *argv } else { b"\0".as_ptr() as *const c_char };
108                    crate::fprintf(
109                        crate::stderr,
110                        b"%s: option requires an argument: %.*s\n\0".as_ptr() as *const c_char,
111                        prog,
112                        clen as c_int,
113                        optchar,
114                    );
115                }
116                return b'?' as c_int;
117            }
118            optarg = *argv.add(optind as usize) as *mut c_char;
119            optind += 1;
120            if __optpos != 0 { optarg = optarg.add(__optpos as usize); }
121            __optpos = 0;
122        }
123    }
124    c as c_int
125}
126
127unsafe fn getopt_permute(argv: *mut *mut c_char, dest: isize, src: isize) {
128    let tmp = *argv.offset(src);
129    let mut i = src;
130    while i > dest {
131        *argv.offset(i) = *argv.offset(i - 1);
132        i -= 1;
133    }
134    *argv.offset(dest) = tmp;
135}
136
137unsafe fn getopt_long_core(
138    argc: c_int,
139    argv: *const *const c_char,
140    optstring: *const c_char,
141    longopts: *const LongOption,
142    idx: *mut c_int,
143    longonly: bool,
144) -> c_int {
145    optarg = core::ptr::null_mut();
146    let cur = *argv.add(optind as usize);
147    let is_long = !longopts.is_null()
148        && *cur == b'-' as c_char
149        && ((longonly && *cur.add(1) != 0 && *cur.add(1) != b'-' as c_char)
150            || (*cur.add(1) == b'-' as c_char && *cur.add(2) != 0));
151    if is_long {
152        let colon = *optstring.add(if *optstring == b'+' as c_char || *optstring == b'-' as c_char { 1 } else { 0 }) == b':' as c_char;
153        let start = cur.add(1);
154        let mut cnt = 0;
155        let mut match_idx = 0usize;
156        let mut arg: *const c_char = core::ptr::null();
157        let mut i = 0usize;
158        loop {
159            let opt = (*longopts.add(i)).name;
160            if opt.is_null() { break; }
161            let mut o = start;
162            if *o == b'-' as c_char { o = o.add(1); }
163            let mut n = opt;
164            while *o != 0 && *o != b'=' as c_char && *o == *n {
165                o = o.add(1);
166                n = n.add(1);
167            }
168            if *o != 0 && *o != b'=' as c_char {
169                i += 1;
170                continue;
171            }
172            arg = o;
173            match_idx = i;
174            if *n == 0 { cnt = 1; break; }
175            cnt += 1;
176            i += 1;
177        }
178        let _ = arg;
179        if cnt == 1 {
180            let opt = arg;
181            optind += 1;
182            let entry = *longopts.add(match_idx);
183            if *opt == b'=' as c_char {
184                if entry.has_arg == 0 {
185                    optopt = entry.val;
186                    if colon || opterr == 0 { return b'?' as c_int; }
187                    crate::fprintf(
188                        crate::stderr,
189                        b"%s: option does not take an argument: %s\n\0".as_ptr() as *const c_char,
190                        *argv,
191                        entry.name,
192                    );
193                    return b'?' as c_int;
194                }
195                optarg = opt.add(1) as *mut c_char;
196            } else if entry.has_arg == 1 {
197                if optind >= argc {
198                    optopt = entry.val;
199                    if colon { return b':' as c_int; }
200                    if opterr == 0 { return b'?' as c_int; }
201                    crate::fprintf(
202                        crate::stderr,
203                        b"%s: option requires an argument: %s\n\0".as_ptr() as *const c_char,
204                        *argv,
205                        entry.name,
206                    );
207                    return b'?' as c_int;
208                }
209                optarg = *argv.add(optind as usize) as *mut c_char;
210                optind += 1;
211            }
212            if !idx.is_null() { *idx = match_idx as c_int; }
213            if !entry.flag.is_null() {
214                *entry.flag = entry.val;
215                return 0;
216            }
217            return entry.val;
218        }
219        if *cur.add(1) == b'-' as c_char {
220            optopt = 0;
221            if !colon && opterr != 0 {
222                crate::fprintf(
223                    crate::stderr,
224                    if cnt != 0 {
225                        b"%s: option is ambiguous: %s\n\0".as_ptr() as *const c_char
226                    } else {
227                        b"%s: unrecognized option: %s\n\0".as_ptr() as *const c_char
228                    },
229                    *argv,
230                    cur.add(2),
231                );
232            }
233            optind += 1;
234            return b'?' as c_int;
235        }
236    }
237    getopt(argc, argv, optstring)
238}
239
240#[no_mangle]
241pub unsafe extern "C" fn getopt_long(
242    argc: c_int,
243    argv: *const *const c_char,
244    optstring: *const c_char,
245    longopts: *const LongOption,
246    idx: *mut c_int,
247) -> c_int {
248    getopt_reset_if_needed();
249    if argc <= 0 || argv.is_null() { return -1; }
250    if optind >= argc || (*argv.add(optind as usize)).is_null() { return -1; }
251    let skipped = optind;
252    if *optstring != b'+' as c_char && *optstring != b'-' as c_char {
253        let mut i = optind;
254        loop {
255            if i >= argc || (*argv.add(i as usize)).is_null() { return -1; }
256            let a = *argv.add(i as usize);
257            if *a == b'-' as c_char && *a.add(1) != 0 { break; }
258            i += 1;
259        }
260        optind = i;
261    }
262    let resumed = optind;
263    let ret = getopt_long_core(argc, argv, optstring, longopts, idx, false);
264    if resumed > skipped {
265        let cnt = optind - resumed;
266        let mut i = 0;
267        while i < cnt {
268            getopt_permute(argv as *mut *mut c_char, skipped as isize, (optind - 1) as isize);
269            i += 1;
270        }
271        optind = skipped + cnt;
272    }
273    ret
274}
275
276#[no_mangle]
277pub unsafe extern "C" fn getopt_long_only(
278    argc: c_int,
279    argv: *const *const c_char,
280    optstring: *const c_char,
281    longopts: *const LongOption,
282    idx: *mut c_int,
283) -> c_int {
284    getopt_reset_if_needed();
285    if argc <= 0 || argv.is_null() { return -1; }
286    if optind >= argc || (*argv.add(optind as usize)).is_null() { return -1; }
287    let skipped = optind;
288    if *optstring != b'+' as c_char && *optstring != b'-' as c_char {
289        let mut i = optind;
290        loop {
291            if i >= argc || (*argv.add(i as usize)).is_null() { return -1; }
292            let a = *argv.add(i as usize);
293            if *a == b'-' as c_char && *a.add(1) != 0 { break; }
294            i += 1;
295        }
296        optind = i;
297    }
298    let resumed = optind;
299    let ret = getopt_long_core(argc, argv, optstring, longopts, idx, true);
300    if resumed > skipped {
301        let cnt = optind - resumed;
302        let mut i = 0;
303        while i < cnt {
304            getopt_permute(argv as *mut *mut c_char, skipped as isize, (optind - 1) as isize);
305            i += 1;
306        }
307        optind = skipped + cnt;
308    }
309    ret
310}