summaryrefslogtreecommitdiff
path: root/lib/mlibc/options/glibc/generic/getopt-stubs.cpp
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2024-03-07 17:28:00 -0500
committerIan Moffett <ian@osmora.org>2024-03-07 17:28:32 -0500
commitbd5969fc876a10b18613302db7087ef3c40f18e1 (patch)
tree7c2b8619afe902abf99570df2873fbdf40a4d1a1 /lib/mlibc/options/glibc/generic/getopt-stubs.cpp
parenta95b38b1b92b172e6cc4e8e56a88a30cc65907b0 (diff)
lib: Add mlibc
Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'lib/mlibc/options/glibc/generic/getopt-stubs.cpp')
-rw-r--r--lib/mlibc/options/glibc/generic/getopt-stubs.cpp253
1 files changed, 253 insertions, 0 deletions
diff --git a/lib/mlibc/options/glibc/generic/getopt-stubs.cpp b/lib/mlibc/options/glibc/generic/getopt-stubs.cpp
new file mode 100644
index 0000000..d13d4eb
--- /dev/null
+++ b/lib/mlibc/options/glibc/generic/getopt-stubs.cpp
@@ -0,0 +1,253 @@
+#include <assert.h>
+#include <bits/ensure.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <frg/optional.hpp>
+#include <mlibc/debug.hpp>
+
+char *optarg;
+int optind = 1;
+int opterr = 1;
+int optopt;
+
+namespace {
+
+int __optpos = 1;
+
+enum GetoptMode {
+ Short,
+ Long,
+ LongOnly,
+};
+
+int getopt_common(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, enum GetoptMode mode) {
+ auto longopt_consume = [&](const char *arg, char *s, int k, bool colon) -> frg::optional<int> {
+ // Consume the option and its argument.
+ if(longopts[k].has_arg == required_argument) {
+ if(s) {
+ // Consume the long option and its argument.
+ optarg = s + 1;
+ optind++;
+ }else if(argv[optind + 1]) {
+ // Consume the long option.
+ optind++;
+
+ // Consume the option's argument.
+ optarg = argv[optind];
+ optind++;
+ }else{
+ /* If an error was detected, and the first character of optstring is not a colon,
+ and the external variable opterr is nonzero (which is the default),
+ getopt() prints an error message. */
+ if(!colon && opterr)
+ fprintf(stderr, "--%s requires an argument.\n", arg);
+ /* If the first character of optstring is a colon (':'), then getopt()
+ returns ':' instead of '?' to indicate a missing option argument. */
+ return colon ? ':' : '?';
+ }
+ }else if(longopts[k].has_arg == optional_argument) {
+ if(s) {
+ // Consume the long option and its argument.
+ optarg = s + 1;
+ optind++;
+ }else{
+ // Consume the long option.
+ optarg = nullptr;
+ optind++;
+ }
+ }else{
+ __ensure(longopts[k].has_arg == no_argument);
+
+ // Consume the long option.
+ optind++;
+ }
+
+ return frg::null_opt;
+ };
+
+ bool colon = optstring[0] == ':';
+ bool stop_at_first_nonarg = (optstring[0] == '+' || getenv("POSIXLY_CORRECT"));
+
+ // glibc extension: Setting optind to zero causes a full reset.
+ // TODO: Should we really reset opterr and the other flags?
+ if(!optind
+#if __MLIBC_BSD_OPTION
+ || optreset
+#endif //__MLIBC_BSD_OPTION
+ ) {
+ optarg = nullptr;
+ optind = 1;
+ opterr = 1;
+ optopt = 0;
+ __optpos = 1;
+#if __MLIBC_BSD_OPTION
+ optreset = 0;
+#endif //__MLIBC_BSD_OPTION
+ }
+
+ auto isOptionArg = [](char *arg){
+ // If the first character of arg '-', and the arg is not exactly
+ // equal to "-" or "--", then the arg is an option argument.
+ return arg[0] == '-' && strcmp(arg, "-") && strcmp(arg, "--");
+ };
+
+ while(optind < argc) {
+ char *arg = argv[optind];
+ if(!isOptionArg(arg)) {
+ if(stop_at_first_nonarg) {
+ return -1;
+ }
+
+ bool further_options = false;
+ int skip = optind;
+
+ for(; skip < argc; ++skip) {
+ if(isOptionArg(argv[skip])) {
+ further_options = true;
+ break;
+ }
+ }
+
+ if(further_options) {
+ optind += skip - optind;
+ continue;
+ } else {
+ optarg = nullptr;
+ return -1;
+ }
+ }
+
+ if(arg[1] == '-') {
+ arg += 2;
+
+ // Determine the end of the option name (vs. the start of the argument).
+ auto s = strchr(arg, '=');
+ size_t n = s ? (s - arg) : strlen(arg);
+
+ int k = -1;
+ for(int i = 0; longopts[i].name; i++) {
+ if(strncmp(arg, longopts[i].name, n) || longopts[i].name[n])
+ continue;
+
+ if(k >= 0) {
+ if(opterr)
+ fprintf(stderr, "Multiple option declaration detected: %s\n", arg);
+ return '?';
+ }
+ k = i;
+ }
+
+ if(k == -1) {
+ if(opterr)
+ fprintf(stderr, "--%s is not a valid option.\n", arg);
+ return '?';
+ }
+
+ if(longindex)
+ *longindex = k;
+
+ if(auto r = longopt_consume(arg, s, k, colon); r)
+ return r.value();
+
+ if(!longopts[k].flag) {
+ return longopts[k].val;
+ }else{
+ *longopts[k].flag = longopts[k].val;
+ return 0;
+ }
+ }else{
+ /* handle short options, i.e. options with only one dash prefixed; e.g. `program -s` */
+ unsigned int i = __optpos;
+ while(true) {
+ if(mode == GetoptMode::LongOnly) {
+ const char *lo_arg = &arg[1];
+ auto s = strchr(lo_arg, '=');
+ size_t n = s ? (s - lo_arg) : strlen(lo_arg);
+ int k = -1;
+
+ for(int longopt = 0; longopts[longopt].name; longopt++) {
+ if(strncmp(lo_arg, longopts[longopt].name, n) || longopts[longopt].name[n])
+ continue;
+
+ if(k >= 0) {
+ if(opterr)
+ fprintf(stderr, "Multiple option declaration detected: %s\n", arg);
+ return '?';
+ }
+
+ k = longopt;
+ }
+
+ if(k != -1) {
+ if(auto r = longopt_consume(lo_arg, s, k, colon); r)
+ return r.value();
+
+ if(!longopts[k].flag) {
+ return longopts[k].val;
+ }else{
+ *longopts[k].flag = longopts[k].val;
+ return 0;
+ }
+ }
+ }
+
+ auto opt = strchr(optstring, arg[i]);
+ if(opt) {
+ if(opt[1] == ':') {
+ bool required = (opt[2] != ':');
+
+ if(arg[i+1]) {
+ optarg = arg + i + 1;
+ } else if(optind + 1 < argc && argv[optind + 1] && (required || argv[optind + 1][0] != '-')) {
+ /* there is an argument to this short option, separated by a space */
+ optarg = argv[optind + 1];
+ optind++;
+ __optpos = 1;
+ } else if(!required) {
+ optarg = nullptr;
+ } else {
+ __optpos = 1;
+ optopt = arg[i];
+ return colon ? ':' : '?';
+ }
+ optind++;
+ } else {
+ if(arg[i+1]) {
+ __optpos++;
+ } else if(arg[i]) {
+ optind++;
+ } else {
+ return -1;
+ }
+ }
+
+ return arg[i];
+ } else {
+ /* If getopt() does not recognize an option character, it prints an error message to stderr,
+ stores the character in optopt, and returns '?'. The calling program may prevent
+ the error message by setting opterr to 0. */
+ optopt = arg[1];
+ if(opterr)
+ fprintf(stderr, "%s is not a valid option.\n", arg);
+ return '?';
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+}
+
+int getopt_long(int argc, char * const argv[], const char *optstring,
+ const struct option *longopts, int *longindex) {
+ return getopt_common(argc, argv, optstring, longopts, longindex, GetoptMode::Long);
+}
+
+int getopt_long_only(int argc, char * const argv[], const char *optstring,
+ const struct option *longopts, int *longindex) {
+ return getopt_common(argc, argv, optstring, longopts, longindex, GetoptMode::LongOnly);
+}