1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2024 Gavin D. Howard and contributors. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * * Redistributions of source code must retain the above copyright notice, this 12 * list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright notice, 15 * this list of conditions and the following disclaimer in the documentation 16 * and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 * ***************************************************************************** 31 * 32 * Adapted from https://github.com/skeeto/optparse 33 * 34 * ***************************************************************************** 35 * 36 * Code for getopt_long() replacement. It turns out that getopt_long() has 37 * different behavior on different platforms. 38 * 39 */ 40 41 #include <assert.h> 42 #include <stdbool.h> 43 #include <stdlib.h> 44 #include <string.h> 45 46 #include <status.h> 47 #include <opt.h> 48 #include <vm.h> 49 50 /** 51 * Returns true if index @a i is the end of the longopts array. 52 * @param longopts The long options array. 53 * @param i The index to test. 54 * @return True if @a i is the last index, false otherwise. 55 */ 56 static inline bool 57 bc_opt_longoptsEnd(const BcOptLong* longopts, size_t i) 58 { 59 return !longopts[i].name && !longopts[i].val; 60 } 61 62 /** 63 * Returns the name of the long option that matches the character @a c. 64 * @param longopts The long options array. 65 * @param c The character to match against. 66 * @return The name of the long option that matches @a c, or "NULL". 67 */ 68 static const char* 69 bc_opt_longopt(const BcOptLong* longopts, int c) 70 { 71 size_t i; 72 73 for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i) 74 { 75 if (longopts[i].val == c) return longopts[i].name; 76 } 77 78 BC_UNREACHABLE 79 80 #if !BC_CLANG 81 return "NULL"; 82 #endif // !BC_CLANG 83 } 84 85 /** 86 * Issues a fatal error for an option parsing failure. 87 * @param err The error. 88 * @param c The character for the failing option. 89 * @param str Either the string for the failing option, or the invalid 90 * option. 91 * @param use_short True if the short option should be used for error printing, 92 * false otherwise. 93 */ 94 static void 95 bc_opt_error(BcErr err, int c, const char* str, bool use_short) 96 { 97 if (err == BC_ERR_FATAL_OPTION) 98 { 99 if (use_short) 100 { 101 char short_str[2]; 102 103 short_str[0] = (char) c; 104 short_str[1] = '\0'; 105 106 bc_error(err, 0, short_str); 107 } 108 else bc_error(err, 0, str); 109 } 110 else bc_error(err, 0, (int) c, str); 111 } 112 113 /** 114 * Returns the type of the long option that matches @a c. 115 * @param longopts The long options array. 116 * @param c The character to match against. 117 * @return The type of the long option as an integer, or -1 if none. 118 */ 119 static int 120 bc_opt_type(const BcOptLong* longopts, char c) 121 { 122 size_t i; 123 124 if (c == ':') return -1; 125 126 for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i) 127 { 128 continue; 129 } 130 131 if (bc_opt_longoptsEnd(longopts, i)) return -1; 132 133 return (int) longopts[i].type; 134 } 135 136 /** 137 * Parses a short option. 138 * @param o The option parser. 139 * @param longopts The long options array. 140 * @return The character for the short option, or -1 if none left. 141 */ 142 static int 143 bc_opt_parseShort(BcOpt* o, const BcOptLong* longopts) 144 { 145 int type; 146 const char* next; 147 const char* option = o->argv[o->optind]; 148 int ret = -1; 149 150 // Make sure to clear these. 151 o->optopt = 0; 152 o->optarg = NULL; 153 154 // Get the next option. 155 option += o->subopt + 1; 156 o->optopt = option[0]; 157 158 // Get the type and the next data. 159 type = bc_opt_type(longopts, option[0]); 160 next = o->argv[o->optind + 1]; 161 162 switch (type) 163 { 164 case -1: 165 case BC_OPT_BC_ONLY: 166 case BC_OPT_DC_ONLY: 167 { 168 // Check for invalid option and barf if so. 169 if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) || 170 (type == BC_OPT_DC_ONLY && BC_IS_BC)) 171 { 172 char str[2] = { 0, 0 }; 173 174 str[0] = option[0]; 175 o->optind += 1; 176 177 bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true); 178 } 179 180 // Fallthrough. 181 BC_FALLTHROUGH 182 } 183 184 case BC_OPT_NONE: 185 { 186 // If there is something else, update the suboption. 187 if (option[1]) o->subopt += 1; 188 else 189 { 190 // Go to the next argument. 191 o->subopt = 0; 192 o->optind += 1; 193 } 194 195 ret = (int) option[0]; 196 197 break; 198 } 199 200 case BC_OPT_REQUIRED_BC_ONLY: 201 { 202 #if DC_ENABLED 203 if (BC_IS_DC) 204 { 205 bc_opt_error(BC_ERR_FATAL_OPTION, option[0], 206 bc_opt_longopt(longopts, option[0]), true); 207 } 208 #endif // DC_ENABLED 209 210 // Fallthrough 211 BC_FALLTHROUGH 212 } 213 214 case BC_OPT_REQUIRED: 215 { 216 // Always go to the next argument. 217 o->subopt = 0; 218 o->optind += 1; 219 220 // Use the next characters, if they exist. 221 if (option[1]) o->optarg = option + 1; 222 else if (next != NULL) 223 { 224 // USe the next. 225 o->optarg = next; 226 o->optind += 1; 227 } 228 // No argument, barf. 229 else 230 { 231 bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0], 232 bc_opt_longopt(longopts, option[0]), true); 233 } 234 235 ret = (int) option[0]; 236 237 break; 238 } 239 } 240 241 return ret; 242 } 243 244 /** 245 * Ensures that a long option argument matches a long option name, regardless of 246 * "=<data>" at the end. 247 * @param name The name to match. 248 * @param option The command-line argument. 249 * @return True if @a option matches @a name, false otherwise. 250 */ 251 static bool 252 bc_opt_longoptsMatch(const char* name, const char* option) 253 { 254 const char* a = option; 255 const char* n = name; 256 257 // Can never match a NULL name. 258 if (name == NULL) return false; 259 260 // Loop through. 261 for (; *a && *n && *a != '='; ++a, ++n) 262 { 263 if (*a != *n) return false; 264 } 265 266 // Ensure they both end at the same place. 267 return (*n == '\0' && (*a == '\0' || *a == '=')); 268 } 269 270 /** 271 * Returns a pointer to the argument of a long option, or NULL if it not in the 272 * same argument. 273 * @param option The option to find the argument of. 274 * @return A pointer to the argument of the option, or NULL if none. 275 */ 276 static const char* 277 bc_opt_longoptsArg(const char* option) 278 { 279 // Find the end or equals sign. 280 for (; *option && *option != '='; ++option) 281 { 282 continue; 283 } 284 285 if (*option == '=') return option + 1; 286 else return NULL; 287 } 288 289 int 290 bc_opt_parse(BcOpt* o, const BcOptLong* longopts) 291 { 292 size_t i; 293 const char* option; 294 bool empty; 295 296 // This just eats empty options. 297 do 298 { 299 option = o->argv[o->optind]; 300 if (option == NULL) return -1; 301 302 empty = !strcmp(option, ""); 303 o->optind += empty; 304 } 305 while (empty); 306 307 // If the option is just a "--". 308 if (BC_OPT_ISDASHDASH(option)) 309 { 310 // Consume "--". 311 o->optind += 1; 312 return -1; 313 } 314 // Parse a short option. 315 else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts); 316 // If the option is not long at this point, we are done. 317 else if (!BC_OPT_ISLONGOPT(option)) return -1; 318 319 // Clear these. 320 o->optopt = 0; 321 o->optarg = NULL; 322 323 // Skip "--" at beginning of the option. 324 option += 2; 325 o->optind += 1; 326 327 // Loop through the valid long options. 328 for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) 329 { 330 const char* name = longopts[i].name; 331 332 // If we have a match... 333 if (bc_opt_longoptsMatch(name, option)) 334 { 335 const char* arg; 336 337 // Get the option char and the argument. 338 o->optopt = longopts[i].val; 339 arg = bc_opt_longoptsArg(option); 340 341 // Error if the option is invalid.. 342 if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) || 343 (longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) || 344 (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC)) 345 { 346 bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false); 347 } 348 349 // Error if we have an argument and should not. 350 if (longopts[i].type == BC_OPT_NONE && arg != NULL) 351 { 352 bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false); 353 } 354 355 // Set the argument, or check the next argument if we don't have 356 // one. 357 if (arg != NULL) o->optarg = arg; 358 else if (longopts[i].type == BC_OPT_REQUIRED || 359 longopts[i].type == BC_OPT_REQUIRED_BC_ONLY) 360 { 361 // Get the next argument. 362 o->optarg = o->argv[o->optind]; 363 364 // All's good if it exists; otherwise, barf. 365 if (o->optarg != NULL) o->optind += 1; 366 else 367 { 368 bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, o->optopt, name, 369 false); 370 } 371 } 372 373 return o->optopt; 374 } 375 } 376 377 // If we reach this point, the option is invalid. 378 bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false); 379 380 BC_UNREACHABLE 381 382 #if !BC_CLANG 383 return -1; 384 #endif // !BC_CLANG 385 } 386 387 void 388 bc_opt_init(BcOpt* o, const char* argv[]) 389 { 390 o->argv = argv; 391 o->optind = 1; 392 o->subopt = 0; 393 o->optarg = NULL; 394 } 395