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