1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2021 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 bc_opt_longoptsEnd(const BcOptLong *longopts, size_t i) { 57 return !longopts[i].name && !longopts[i].val; 58 } 59 60 /** 61 * Returns the name of the long option that matches the character @a c. 62 * @param longopts The long options array. 63 * @param c The character to match against. 64 * @return The name of the long option that matches @a c, or "NULL". 65 */ 66 static const char* bc_opt_longopt(const BcOptLong *longopts, int c) { 67 68 size_t i; 69 70 for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i) { 71 if (longopts[i].val == c) return longopts[i].name; 72 } 73 74 BC_UNREACHABLE 75 76 return "NULL"; 77 } 78 79 /** 80 * Issues a fatal error for an option parsing failure. 81 * @param err The error. 82 * @param c The character for the failing option. 83 * @param str Either the string for the failing option, or the invalid 84 * option. 85 * @param use_short True if the short option should be used for error printing, 86 * false otherwise. 87 */ 88 static void bc_opt_error(BcErr err, int c, const char *str, bool use_short) { 89 90 if (err == BC_ERR_FATAL_OPTION) { 91 92 if (use_short) { 93 94 char short_str[2]; 95 96 short_str[0] = (char) c; 97 short_str[1] = '\0'; 98 99 bc_error(err, 0, short_str); 100 } 101 else bc_error(err, 0, str); 102 } 103 else bc_error(err, 0, (int) c, str); 104 } 105 106 /** 107 * Returns the type of the long option that matches @a c. 108 * @param longopts The long options array. 109 * @param c The character to match against. 110 * @return The type of the long option as an integer, or -1 if none. 111 */ 112 static int bc_opt_type(const BcOptLong *longopts, char c) { 113 114 size_t i; 115 116 if (c == ':') return -1; 117 118 for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i); 119 120 if (bc_opt_longoptsEnd(longopts, i)) return -1; 121 122 return (int) longopts[i].type; 123 } 124 125 /** 126 * Parses a short option. 127 * @param o The option parser. 128 * @param longopts The long options array. 129 * @return The character for the short option, or -1 if none left. 130 */ 131 static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) { 132 133 int type; 134 char *next; 135 char *option = o->argv[o->optind]; 136 int ret = -1; 137 138 // Make sure to clear these. 139 o->optopt = 0; 140 o->optarg = NULL; 141 142 // Get the next option. 143 option += o->subopt + 1; 144 o->optopt = option[0]; 145 146 // Get the type and the next data. 147 type = bc_opt_type(longopts, option[0]); 148 next = o->argv[o->optind + 1]; 149 150 switch (type) { 151 152 case -1: 153 case BC_OPT_BC_ONLY: 154 case BC_OPT_DC_ONLY: 155 { 156 // Check for invalid option and barf if so. 157 if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) || 158 (type == BC_OPT_DC_ONLY && BC_IS_BC)) 159 { 160 char str[2] = {0, 0}; 161 162 str[0] = option[0]; 163 o->optind += 1; 164 165 bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true); 166 } 167 } 168 // Fallthrough. 169 BC_FALLTHROUGH 170 171 case BC_OPT_NONE: 172 { 173 // If there is something else, update the suboption. 174 if (option[1]) o->subopt += 1; 175 else { 176 177 // Go to the next argument. 178 o->subopt = 0; 179 o->optind += 1; 180 } 181 182 ret = (int) option[0]; 183 184 break; 185 } 186 187 case BC_OPT_REQUIRED_BC_ONLY: 188 { 189 if (BC_IS_DC) 190 bc_opt_error(BC_ERR_FATAL_OPTION, option[0], 191 bc_opt_longopt(longopts, option[0]), true); 192 } 193 // Fallthrough 194 BC_FALLTHROUGH 195 196 case BC_OPT_REQUIRED: 197 { 198 // Always go to the next argument. 199 o->subopt = 0; 200 o->optind += 1; 201 202 // Use the next characters, if they exist. 203 if (option[1]) o->optarg = option + 1; 204 else if (next != NULL) { 205 206 // USe the next. 207 o->optarg = next; 208 o->optind += 1; 209 } 210 // No argument, barf. 211 else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0], 212 bc_opt_longopt(longopts, option[0]), true); 213 214 215 ret = (int) option[0]; 216 217 break; 218 } 219 } 220 221 return ret; 222 } 223 224 /** 225 * Ensures that a long option argument matches a long option name, regardless of 226 * "=<data>" at the end. 227 * @param name The name to match. 228 * @param option The command-line argument. 229 * @return True if @a option matches @a name, false otherwise. 230 */ 231 static bool bc_opt_longoptsMatch(const char *name, const char *option) { 232 233 const char *a = option, *n = name; 234 235 // Can never match a NULL name. 236 if (name == NULL) return false; 237 238 // Loop through. 239 for (; *a && *n && *a != '='; ++a, ++n) { 240 if (*a != *n) return false; 241 } 242 243 // Ensure they both end at the same place. 244 return (*n == '\0' && (*a == '\0' || *a == '=')); 245 } 246 247 /** 248 * Returns a pointer to the argument of a long option, or NULL if it not in the 249 * same argument. 250 * @param option The option to find the argument of. 251 * @return A pointer to the argument of the option, or NULL if none. 252 */ 253 static char* bc_opt_longoptsArg(char *option) { 254 255 // Find the end or equals sign. 256 for (; *option && *option != '='; ++option); 257 258 if (*option == '=') return option + 1; 259 else return NULL; 260 } 261 262 int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) { 263 264 size_t i; 265 char *option; 266 bool empty; 267 268 // This just eats empty options. 269 do { 270 271 option = o->argv[o->optind]; 272 if (option == NULL) return -1; 273 274 empty = !strcmp(option, ""); 275 o->optind += empty; 276 277 } while (empty); 278 279 // If the option is just a "--". 280 if (BC_OPT_ISDASHDASH(option)) { 281 282 // Consume "--". 283 o->optind += 1; 284 return -1; 285 } 286 // Parse a short option. 287 else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts); 288 // If the option is not long at this point, we are done. 289 else if (!BC_OPT_ISLONGOPT(option)) return -1; 290 291 // Clear these. 292 o->optopt = 0; 293 o->optarg = NULL; 294 295 // Skip "--" at beginning of the option. 296 option += 2; 297 o->optind += 1; 298 299 // Loop through the valid long options. 300 for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) { 301 302 const char *name = longopts[i].name; 303 304 // If we have a match... 305 if (bc_opt_longoptsMatch(name, option)) { 306 307 char *arg; 308 309 // Get the option char and the argument. 310 o->optopt = longopts[i].val; 311 arg = bc_opt_longoptsArg(option); 312 313 // Error if the option is invalid.. 314 if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) || 315 (longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) || 316 (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC)) 317 { 318 bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false); 319 } 320 321 // Error if we have an argument and should not. 322 if (longopts[i].type == BC_OPT_NONE && arg != NULL) 323 { 324 bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false); 325 } 326 327 // Set the argument, or check the next argument if we don't have 328 // one. 329 if (arg != NULL) o->optarg = arg; 330 else if (longopts[i].type == BC_OPT_REQUIRED || 331 longopts[i].type == BC_OPT_REQUIRED_BC_ONLY) 332 { 333 // Get the next argument. 334 o->optarg = o->argv[o->optind]; 335 336 // All's good if it exists; otherwise, barf. 337 if (o->optarg != NULL) o->optind += 1; 338 else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, 339 o->optopt, name, false); 340 } 341 342 return o->optopt; 343 } 344 } 345 346 // If we reach this point, the option is invalid. 347 bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false); 348 349 BC_UNREACHABLE 350 351 return -1; 352 } 353 354 void bc_opt_init(BcOpt *o, char *argv[]) { 355 o->argv = argv; 356 o->optind = 1; 357 o->subopt = 0; 358 o->optarg = NULL; 359 } 360