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