/*1* *****************************************************************************2*3* SPDX-License-Identifier: BSD-2-Clause4*5* Copyright (c) 2018-2025 Gavin D. Howard and contributors.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions are met:9*10* * Redistributions of source code must retain the above copyright notice, this11* list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright notice,14* this list of conditions and the following disclaimer in the documentation15* and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"18* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE21* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR22* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF23* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS24* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN25* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)26* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE27* POSSIBILITY OF SUCH DAMAGE.28*29* *****************************************************************************30*31* Adapted from https://github.com/skeeto/optparse32*33* *****************************************************************************34*35* Code for getopt_long() replacement. It turns out that getopt_long() has36* different behavior on different platforms.37*38*/3940#include <assert.h>41#include <stdbool.h>42#include <stdlib.h>43#include <string.h>4445#include <status.h>46#include <opt.h>47#include <vm.h>4849/**50* Returns true if index @a i is the end of the longopts array.51* @param longopts The long options array.52* @param i The index to test.53* @return True if @a i is the last index, false otherwise.54*/55static inline bool56bc_opt_longoptsEnd(const BcOptLong* longopts, size_t i)57{58return !longopts[i].name && !longopts[i].val;59}6061/**62* Returns the name of the long option that matches the character @a c.63* @param longopts The long options array.64* @param c The character to match against.65* @return The name of the long option that matches @a c, or "NULL".66*/67static const char*68bc_opt_longopt(const BcOptLong* longopts, int c)69{70size_t i;7172for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i)73{74if (longopts[i].val == c) return longopts[i].name;75}7677BC_UNREACHABLE7879#if !BC_CLANG80return "NULL";81#endif // !BC_CLANG82}8384/**85* Issues a fatal error for an option parsing failure.86* @param err The error.87* @param c The character for the failing option.88* @param str Either the string for the failing option, or the invalid89* option.90* @param use_short True if the short option should be used for error printing,91* false otherwise.92*/93static void94bc_opt_error(BcErr err, int c, const char* str, bool use_short)95{96if (err == BC_ERR_FATAL_OPTION)97{98if (use_short)99{100char short_str[2];101102short_str[0] = (char) c;103short_str[1] = '\0';104105bc_error(err, 0, short_str);106}107else bc_error(err, 0, str);108}109else bc_error(err, 0, (int) c, str);110}111112/**113* Returns the type of the long option that matches @a c.114* @param longopts The long options array.115* @param c The character to match against.116* @return The type of the long option as an integer, or -1 if none.117*/118static int119bc_opt_type(const BcOptLong* longopts, char c)120{121size_t i;122123if (c == ':') return -1;124125for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i)126{127continue;128}129130if (bc_opt_longoptsEnd(longopts, i)) return -1;131132return (int) longopts[i].type;133}134135/**136* Parses a short option.137* @param o The option parser.138* @param longopts The long options array.139* @return The character for the short option, or -1 if none left.140*/141static int142bc_opt_parseShort(BcOpt* o, const BcOptLong* longopts)143{144int type;145const char* next;146const char* option = o->argv[o->optind];147int ret = -1;148149// Make sure to clear these.150o->optopt = 0;151o->optarg = NULL;152153// Get the next option.154option += o->subopt + 1;155o->optopt = option[0];156157// Get the type and the next data.158type = bc_opt_type(longopts, option[0]);159next = o->argv[o->optind + 1];160161switch (type)162{163case -1:164case BC_OPT_BC_ONLY:165case BC_OPT_DC_ONLY:166{167// Check for invalid option and barf if so.168if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) ||169(type == BC_OPT_DC_ONLY && BC_IS_BC))170{171char str[2] = { 0, 0 };172173str[0] = option[0];174o->optind += 1;175176bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true);177}178179// Fallthrough.180BC_FALLTHROUGH181}182183case BC_OPT_NONE:184{185// If there is something else, update the suboption.186if (option[1]) o->subopt += 1;187else188{189// Go to the next argument.190o->subopt = 0;191o->optind += 1;192}193194ret = (int) option[0];195196break;197}198199case BC_OPT_REQUIRED_BC_ONLY:200{201#if DC_ENABLED202if (BC_IS_DC)203{204bc_opt_error(BC_ERR_FATAL_OPTION, option[0],205bc_opt_longopt(longopts, option[0]), true);206}207#endif // DC_ENABLED208209// Fallthrough210BC_FALLTHROUGH211}212213case BC_OPT_REQUIRED:214{215// Always go to the next argument.216o->subopt = 0;217o->optind += 1;218219// Use the next characters, if they exist.220if (option[1]) o->optarg = option + 1;221else if (next != NULL)222{223// USe the next.224o->optarg = next;225o->optind += 1;226}227// No argument, barf.228else229{230bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0],231bc_opt_longopt(longopts, option[0]), true);232}233234ret = (int) option[0];235236break;237}238}239240return ret;241}242243/**244* Ensures that a long option argument matches a long option name, regardless of245* "=<data>" at the end.246* @param name The name to match.247* @param option The command-line argument.248* @return True if @a option matches @a name, false otherwise.249*/250static bool251bc_opt_longoptsMatch(const char* name, const char* option)252{253const char* a = option;254const char* n = name;255256// Can never match a NULL name.257if (name == NULL) return false;258259// Loop through.260for (; *a && *n && *a != '='; ++a, ++n)261{262if (*a != *n) return false;263}264265// Ensure they both end at the same place.266return (*n == '\0' && (*a == '\0' || *a == '='));267}268269/**270* Returns a pointer to the argument of a long option, or NULL if it not in the271* same argument.272* @param option The option to find the argument of.273* @return A pointer to the argument of the option, or NULL if none.274*/275static const char*276bc_opt_longoptsArg(const char* option)277{278// Find the end or equals sign.279for (; *option && *option != '='; ++option)280{281continue;282}283284if (*option == '=') return option + 1;285else return NULL;286}287288int289bc_opt_parse(BcOpt* o, const BcOptLong* longopts)290{291size_t i;292const char* option;293bool empty;294295// This just eats empty options.296do297{298option = o->argv[o->optind];299if (option == NULL) return -1;300301empty = !strcmp(option, "");302o->optind += empty;303}304while (empty);305306// If the option is just a "--".307if (BC_OPT_ISDASHDASH(option))308{309// Consume "--".310o->optind += 1;311return -1;312}313// Parse a short option.314else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts);315// If the option is not long at this point, we are done.316else if (!BC_OPT_ISLONGOPT(option)) return -1;317318// Clear these.319o->optopt = 0;320o->optarg = NULL;321322// Skip "--" at beginning of the option.323option += 2;324o->optind += 1;325326// Loop through the valid long options.327for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++)328{329const char* name = longopts[i].name;330331// If we have a match...332if (bc_opt_longoptsMatch(name, option))333{334const char* arg;335336// Get the option char and the argument.337o->optopt = longopts[i].val;338arg = bc_opt_longoptsArg(option);339340// Error if the option is invalid..341if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) ||342(longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) ||343(longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))344{345bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false);346}347348// Error if we have an argument and should not.349if (longopts[i].type == BC_OPT_NONE && arg != NULL)350{351bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false);352}353354// Set the argument, or check the next argument if we don't have355// one.356if (arg != NULL) o->optarg = arg;357else if (longopts[i].type == BC_OPT_REQUIRED ||358longopts[i].type == BC_OPT_REQUIRED_BC_ONLY)359{360// Get the next argument.361o->optarg = o->argv[o->optind];362363// All's good if it exists; otherwise, barf.364if (o->optarg != NULL) o->optind += 1;365else366{367bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, o->optopt, name,368false);369}370}371372return o->optopt;373}374}375376// If we reach this point, the option is invalid.377bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false);378379BC_UNREACHABLE380381#if !BC_CLANG382return -1;383#endif // !BC_CLANG384}385386void387bc_opt_init(BcOpt* o, const char* argv[])388{389o->argv = argv;390o->optind = 1;391o->subopt = 0;392o->optarg = NULL;393}394395396