diff options
author | Oxore <oxore@protonmail.com> | 2024-04-30 01:38:42 +0300 |
---|---|---|
committer | Oxore <oxore@protonmail.com> | 2024-05-19 18:21:40 +0300 |
commit | 71b89bc9ceb59f2603cf4b0635849269597a4823 (patch) | |
tree | 7b53520d3f1306f71c2083cf8b6d9dbb749249da | |
parent | 47ffe952bcfc31a78c16be8620109955fdc17f2f (diff) |
Impl --stop and --gdb options
-rw-r--r-- | CMakeLists.txt | 22 | ||||
-rw-r--r-- | chardev.cpp | 50 | ||||
-rw-r--r-- | chardev.hpp | 32 | ||||
-rw-r--r-- | emulator.cpp | 246 | ||||
-rw-r--r-- | optparse/UNLICENSE | 24 | ||||
-rw-r--r-- | optparse/optparse.h | 403 |
6 files changed, 700 insertions, 77 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 487b6fd..6ba5328 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS True) set(emulator_sources bus.cpp + chardev.cpp emulator.cpp gdbremote_parser.cpp graphics.cpp @@ -40,6 +41,7 @@ add_library(musashi_m68k OBJECT ${musashi_m68k_sources}) target_include_directories(musashi_m68k PRIVATE musashi-m68k ${CMAKE_CURRENT_BINARY_DIR} + . ) # TODO make SDL2 optional for headless mode @@ -76,15 +78,14 @@ target_compile_options(emulator PRIVATE $<$<CONFIG:DEBUG>:${common_debug_flags}> ) -target_link_options(emulator PRIVATE - $<$<CONFIG:DEBUG>:${common_debug_flags}> - ) +target_link_options(emulator PRIVATE $<$<CONFIG:DEBUG>:${common_debug_flags}>) target_link_libraries(emulator musashi_m68k) # TODO make SDL2 optional for headless mode -target_include_directories(emulator PRIVATE ${SDL2_INCLUDE_DIRS}) +target_include_directories(emulator PRIVATE ${SDL2_INCLUDE_DIRS} .) # TODO make SDL2 optional for headless mode target_link_libraries(emulator ${SDL2_LIBRARIES}) target_compile_definitions(emulator PRIVATE + $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2> DEBUG_TRACE_INSTRUCTIONS=0 DEBUG_TRACE_GDB_REMOTE=0 DEBUG_TRACE_VDP_ACCESS=1 @@ -92,13 +93,6 @@ target_compile_definitions(emulator PRIVATE HAS_GRAPHICS=1 ) - -## Target for GDB Remote Debugging protocol implementation testing -#add_executable(gdbremote -# gdbremote.cpp -# gdbremote_parser.cpp -# ) - -include_directories( - . - ) +# Target for GDB Remote Debugging protocol implementation testing +#add_executable(gdbremote gdbremote.cpp gdbremote_parser.cpp) +#target_include_directories(gdbremote PRIVATE .) diff --git a/chardev.cpp b/chardev.cpp new file mode 100644 index 0000000..1d587b9 --- /dev/null +++ b/chardev.cpp @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "chardev.hpp" + +#include <cstring> +#include <cstdlib> + +static bool startsWith(const char* str, const char* pattern) +{ + const size_t pattern_len = strlen(pattern); + const size_t str_len = strlen(str); + if (str_len < pattern_len) { + return false; + } + return 0 == memcmp(str, pattern, pattern_len); +} + +static CharDev parseTcp(const char* path) +{ + // We need to make sure there is at least one more ':' delimiter in the + // string + const char* rest = strchr(path + (sizeof "tcp:") - 1, ':'); + if (rest == nullptr) { + return CharDev::Invalid(path); + } + const char* port_str; + // Find last ':' before string ends, i.e. when strchr returns NULL + do { + port_str = rest + 1; + rest = strchr(port_str, ':'); + } while (rest); + const unsigned long port = strtoul(port_str, nullptr, 0); + if (port <= UINT16_MAX) { + path += (sizeof "tcp:") - 1; + return CharDev::Tcp(path, port_str - 1 - path, port); + } + return CharDev::Invalid(path); +} + +CharDev CharDev::Parse(const char* path) +{ + if (0 == strcmp(path, "pty")) { + return CharDev::Pty(); + } + if (startsWith(path, "tcp:")) { + return parseTcp(path); + } + return CharDev::Invalid(path); +} diff --git a/chardev.hpp b/chardev.hpp new file mode 100644 index 0000000..0a0d9df --- /dev/null +++ b/chardev.hpp @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#pragma once + +#include <cstdint> +#include <cstddef> + +enum class CharDevType: uint8_t { + kUndefined = 0, + kPty, ///< Creates new PTY + kTcp, ///< Binds and listens to specified address and port +}; + +struct CharDev { + CharDevType type{}; + const char* path{}; + int fd{-1}; + size_t path_len{}; + uint16_t port{}; + static CharDev Invalid(const char* path = nullptr) + { + return CharDev{CharDevType::kUndefined, path}; + } + static CharDev Pty(void) { return CharDev{CharDevType::kPty}; } + static CharDev Tcp(const char* host, size_t path_len, uint16_t port) + { + return CharDev{CharDevType::kTcp, host, -1, path_len, port}; + } + static CharDev Parse(const char* path); +}; + diff --git a/emulator.cpp b/emulator.cpp index 72d5184..e3ac6bb 100644 --- a/emulator.cpp +++ b/emulator.cpp @@ -1,6 +1,19 @@ /* SPDX-License-Identifier: Unlicense */ +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include "optparse/optparse.h" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "chardev.hpp" #include "bus.hpp" #include "graphics.hpp" #include "vdp.hpp" @@ -22,9 +35,9 @@ #include <ctime> #include <fcntl.h> #include <sys/socket.h> -#include <memory> #include <unistd.h> #include <signal.h> +#include <netdb.h> #if !defined(DEBUG_TRACE_INSTRUCTIONS) # define DEBUG_TRACE_INSTRUCTIONS 0 @@ -85,15 +98,6 @@ static int set_socket_reuseaddr(const int socket_fd) return setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, len); } -static inline struct sockaddr_in sockaddr_in_any_ip_with_port(const uint16_t port) -{ - struct sockaddr_in server{}; - server.sin_family = AF_INET; - server.sin_addr.s_addr = INADDR_ANY; - server.sin_port = htons(port); - return server; -} - static inline int SetNonblock(const int fd) { const int flags = fcntl(fd, F_GETFL); @@ -107,36 +111,67 @@ static inline int SetNonblock(const int fd) return 0; } -static int setup_socket(const uint16_t port) +static int setup_socket( + const char* host, uint16_t port, char* addrstr, size_t addrstr_size) { - // This creates the socket - or quits - const int socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) { - perror("Could not create socket"); + struct addrinfo hints{}, * result{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + char port_str[6]{}; + snprintf(port_str, sizeof(port_str), "%u", port); + const int gai_ret = getaddrinfo(host, port_str, &hints, &result); + if (gai_ret != 0) { + fprintf(stderr, "getaddrinfo(%s): %s\n", host, gai_strerror(gai_ret)); return -1; } - // Set O_NONBLOCK for socket - const int ret = SetNonblock(socket_fd); - if (ret) { - fprintf(stderr, "fcntl(socket_fd, F_SETFL O_NONBLOCK): %s\n", strerror(ret)); - close(socket_fd); - return -1; - } - // Make TCP port reusable in case of kill or crash. - // Deliberate explanation: https://stackoverflow.com/a/3233022 - if (set_socket_reuseaddr(socket_fd) == -1) { - perror("setsockopt failed"); + int socket_fd = -1; + for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) { + socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (socket_fd == -1) + continue; + // Set O_NONBLOCK for the socket + const int ret = SetNonblock(socket_fd); + if (ret) { + fprintf(stderr, "fcntl(socket_fd, F_SETFL O_NONBLOCK): %s\n", strerror(ret)); + close(socket_fd); + socket_fd = -1; + break; + } + // Make TCP port reusable in case of kill or crash. + // Deliberate explanation: https://stackoverflow.com/a/3233022 + if (set_socket_reuseaddr(socket_fd) == -1) { + perror("setsockopt failed"); + close(socket_fd); + socket_fd = -1; + break; + } + if (0 == bind(socket_fd, rp->ai_addr, rp->ai_addrlen)) { + void* addr_ptr; + if (rp->ai_addr->sa_family == AF_INET) { + addr_ptr = reinterpret_cast<void*>( + &((struct sockaddr_in*)rp->ai_addr)->sin_addr); + } else if (rp->ai_addr->sa_family == AF_INET6) { + addr_ptr = reinterpret_cast<void*>( + &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr); + } else { + assert(false); + } + inet_ntop(rp->ai_addr->sa_family, addr_ptr, addrstr, addrstr_size); + addrstr[addrstr_size - 1] = '\0'; + break; + } + fprintf(stderr, "Binding to %s:%u failed\n", addrstr, port); close(socket_fd); - return -1; + socket_fd = -1; } - puts("Socket created"); - const struct sockaddr_in server = sockaddr_in_any_ip_with_port(port); - if (bind(socket_fd, (struct sockaddr *)&server, sizeof(server)) == -1) { - perror("Bind failed"); - close(socket_fd); + freeaddrinfo(result); + if (socket_fd == -1) { + fprintf(stderr, "Could not bind or create socket: " + "getaddrinfo did not give any suitable entry\n"); return -1; } - printf("Binding to 0.0.0.0:%u done\n", port); + fprintf(stderr, "Binding to %s:%u done\n", addrstr, port); return socket_fd; } @@ -451,10 +486,28 @@ static void RunSingleVideoCycle(M68KDebuggingControl& m68k_debug, Graphics& grap g_cycles_counter += m68k_execute(400000); } -int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics) +static int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics, CharDev &gdb_chardev) { - const int port = 3333; - const int socket_fd = setup_socket(port); + if (gdb_chardev.type == CharDevType::kUndefined) { + while (!quit) { + RunSingleVideoCycle(m68k_debug, graphics); + } + return EXIT_SUCCESS; + } + constexpr size_t addrstr_size = 160; + char addrstr[addrstr_size]{}; + const int port = gdb_chardev.port ? gdb_chardev.port : 3333; + int socket_fd; + if (gdb_chardev.path_len) { + char *host = new (std::nothrow) char[gdb_chardev.path_len + 1]; + assert(host); + memcpy(host, gdb_chardev.path, gdb_chardev.path_len); + host[gdb_chardev.path_len] = '\0'; + socket_fd = setup_socket(host, port, addrstr, addrstr_size); + delete [] host; + } else { + socket_fd = setup_socket("0.0.0.0", port, addrstr, addrstr_size); + } if (socket_fd == -1) return EXIT_FAILURE; // Mark socket as listener @@ -463,16 +516,24 @@ int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics) close(socket_fd); return EXIT_FAILURE; } - printf("Listening TCP 0.0.0.0:%u\n", port); + printf("Listening TCP %s:%u\n", addrstr, port); while (!quit) { struct sockaddr client_address{}; socklen_t address_len{}; const int conn_fd = accept(socket_fd, &client_address, &address_len); - if (conn_fd == -1 ) { + if (conn_fd == -1) { + gdb_chardev.fd = conn_fd; if (errno == EWOULDBLOCK) { - // TODO turn off O_NONBLOCK and poll instead of sleep, only use - // nonlock when freerunning - clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); + if (m68k_debug.IsRunning()) { + RunSingleVideoCycle(m68k_debug, graphics); + if (m68k_debug.HasBreakpoint()) { + m68k_debug.SetRunning(false); + puts("ResetPendingBreakpoint"); + m68k_debug.ResetPendingBreakpoint(); + } + } else { + clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); + } continue; } perror("Accept failed"); @@ -512,57 +573,116 @@ int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics) if (send(conn_fd, &response[0], response.length(), 0) == -1) perror("Send failed (response)"); } + } else { + clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); } - // TODO turn off O_NONBLOCK and poll instead of sleep, only use - // nonlock when freerunning - clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr); } close(conn_fd); + gdb_chardev.fd = -1; g_no_ack_mode = false; // TODO move to GDB::ExchangeContext } close(socket_fd); return 0; } -void sigint_handler(int sig) +static void sigint_handler(int sig) { printf("Signal %d received, exiting\n",sig); quit = true; } -int main(int argc, char* argv[]) +static void PrintUsage(FILE *s, const char *argv0) { - if (argc != 2) - { - printf("Usage: %s <program file>\n", argv[0]); - exit(-1); - } - - FILE* const fhandle = fopen(argv[1], "rb"); + // Please, keep all lines in 80 columns range when printed. + fprintf(s, + "Usage: %s [options] <program-file-name>\n" + "Options:\n" + " -h, --help Show this message.\n" + " -g, --gdb[=DEV] Accept GDB connection on DEV (default: 'tcp::3333').\n" + " -S, --stop Freeze CPU at startup (use 'c' in GDB to start\n" + " execution).\n" + , argv0); +} - if (fhandle == NULL) +int main(int, char* argv[]) +{ + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"gdb", 'g', OPTPARSE_OPTIONAL}, + {"stop", 'S', OPTPARSE_NONE}, + {}, + }; + struct CharDev gdb_chardev{}; + const char *program_file_name = nullptr; + bool stop = false; + struct optparse options; + optparse_init(&options, argv); + // Parse opts + int option; + while ((option = optparse_long(&options, longopts, nullptr)) != -1) { + switch (option) { + case 'h': + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; + break; + case 'g': + { + const char *path = options.optarg ? options.optarg : "tcp::3333"; + gdb_chardev = CharDev::Parse(path); + if (gdb_chardev.type == CharDevType::kPty) { + fprintf(stderr, "main: PTY chardev currently is not supported\n"); + return EXIT_FAILURE; + } else if (gdb_chardev.type == CharDevType::kUndefined) { + fprintf(stderr, "invalid chardev specified: \"%s\"\n", path); + return EXIT_FAILURE; + } + } + break; + case 'S': + stop = true; + printf("stop=%s\n", stop ? "true" : "false"); + break; + case '?': + fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); + return EXIT_FAILURE; + } + } + // Parse input file name + char *arg; + while ((arg = optparse_arg(&options))) { + if (program_file_name == nullptr) { + program_file_name = arg; + } else { + fprintf(stderr, "error: too many free arguments provided\n"); + return EXIT_FAILURE; + } + } + if (program_file_name == nullptr) { + fprintf(stderr, "main: Error: no program file name specified, see usage below.\n"); + PrintUsage(stderr, argv[0]); + return EXIT_FAILURE; + } + FILE* const fhandle = fopen(program_file_name, "rb"); + if (fhandle == nullptr) { exit_error("Unable to open %s", argv[1]); - + } const size_t fread_ret = fread(g_rom, 1, ROM_SIZE, fhandle); - if (fread_ret <= 0) + if (fread_ret <= 0) { exit_error("Error reading %s", argv[1]); + } printf("Read into ROM %zu bytes\n", fread_ret); - struct sigaction sa; sa.sa_handler = sigint_handler; - sigaction(SIGINT, &sa, NULL); - + sigaction(SIGINT, &sa, nullptr); Graphics graphics{}; if (!graphics.IsOk()) { return EXIT_FAILURE; } - m68k_init(); m68k_set_cpu_type(M68K_CPU_TYPE_68000); m68k_pulse_reset(); g_cycles_counter += m68k_execute(1); // Skip reset cycles - - emulator(g_m68k_debug, graphics); - + g_m68k_debug.SetRunning(!stop); + emulator(g_m68k_debug, graphics, gdb_chardev); return EXIT_SUCCESS; } diff --git a/optparse/UNLICENSE b/optparse/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/optparse/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/> diff --git a/optparse/optparse.h b/optparse/optparse.h new file mode 100644 index 0000000..8d6c0a9 --- /dev/null +++ b/optparse/optparse.h @@ -0,0 +1,403 @@ +/* Optparse --- portable, reentrant, embeddable, getopt-like option parser + * + * This is free and unencumbered software released into the public domain. + * + * To get the implementation, define OPTPARSE_IMPLEMENTATION. + * Optionally define OPTPARSE_API to control the API's visibility + * and/or linkage (static, __attribute__, __declspec). + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one option string. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another option + * string. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The option string is the + * same and the fields have the same names as the getopt() global + * variables (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +#ifndef OPTPARSE_API +# define OPTPARSE_API +#endif + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + char *optarg; + char errmsg[64]; + int subopt; +}; + +enum optparse_argtype { + OPTPARSE_NONE, + OPTPARSE_REQUIRED, + OPTPARSE_OPTIONAL +}; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +OPTPARSE_API +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by no colons means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +OPTPARSE_API +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +OPTPARSE_API +int optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or NULL for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +OPTPARSE_API +char *optparse_arg(struct optparse *options); + +/* Implementation */ +#ifdef OPTPARSE_IMPLEMENTATION + +#define OPTPARSE_MSG_INVALID "invalid option" +#define OPTPARSE_MSG_MISSING "option requires an argument" +#define OPTPARSE_MSG_TOOMANY "option takes no arguments" + +static int +optparse_error(struct optparse *options, const char *msg, const char *data) +{ + unsigned p = 0; + const char *sep = " -- '"; + while (*msg) + options->errmsg[p++] = *msg++; + while (*sep) + options->errmsg[p++] = *sep++; + while (p < sizeof(options->errmsg) - 2 && *data) + options->errmsg[p++] = *data++; + options->errmsg[p++] = '\''; + options->errmsg[p++] = '\0'; + return '?'; +} + +OPTPARSE_API +void +optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = argv[0] != 0; + options->subopt = 0; + options->optarg = 0; + options->errmsg[0] = '\0'; +} + +static int +optparse_is_dashdash(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static int +optparse_is_shortopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static int +optparse_is_longopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +optparse_permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + int i; + for (i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static int +optparse_argtype(const char *optstring, char c) +{ + int count = OPTPARSE_NONE; + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +OPTPARSE_API +int +optparse(struct optparse *options, const char *optstring) +{ + int type; + char *next; + char *option = options->argv[options->optind]; + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (!optparse_is_shortopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse(options, optstring); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + type = optparse_argtype(optstring, option[0]); + next = options->argv[options->optind + 1]; + switch (type) { + case -1: { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optind++; + return optparse_error(options, OPTPARSE_MSG_INVALID, str); + } + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != 0) { + options->optarg = next; + options->optind++; + } else { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optarg = 0; + return optparse_error(options, OPTPARSE_MSG_MISSING, str); + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = 0; + return option[0]; + } + return 0; +} + +OPTPARSE_API +char * +optparse_arg(struct optparse *options) +{ + char *option = options->argv[options->optind]; + options->subopt = 0; + if (option != 0) + options->optind++; + return option; +} + +static int +optparse_longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static void +optparse_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + if (longopts[i].shortname && longopts[i].shortname < 127) { + int a; + *p++ = longopts[i].shortname; + for (a = 0; a < (int)longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +optparse_longopts_match(const char *longname, const char *option) +{ + const char *a = option, *n = longname; + if (longname == 0) + return 0; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static char * +optparse_longopts_arg(char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return 0; +} + +static int +optparse_long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int result; + char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + optparse_from_long(longopts, optstring); + result = optparse(options, optstring); + if (longindex != 0) { + *longindex = -1; + if (result != -1) { + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + } + return result; +} + +OPTPARSE_API +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int i; + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (optparse_is_shortopt(option)) { + return optparse_long_fallback(options, longopts, longindex); + } else if (!optparse_is_longopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse_long(options, longopts, longindex); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + option += 2; /* skip "--" */ + options->optind++; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (optparse_longopts_match(name, option)) { + char *arg; + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + arg = optparse_longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { + return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); + } if (arg != 0) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind]; + if (options->optarg == 0) + return optparse_error(options, OPTPARSE_MSG_MISSING, name); + else + options->optind++; + } + return options->optopt; + } + } + return optparse_error(options, OPTPARSE_MSG_INVALID, option); +} + +#endif /* OPTPARSE_IMPLEMENTATION */ +#endif /* OPTPARSE_H */ |