Skip to main content

c/
getopt_impl.rs

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