From fde4d4662ebe3cef1336bec4581e62e075c16883 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Mon, 11 Aug 2025 18:36:46 +0300 Subject: [PATCH] LibC: Implement getopt_long{,_only} Few ports attempt to use this so lets add them :D --- userspace/libraries/LibC/CMakeLists.txt | 1 + userspace/libraries/LibC/getopt.cpp | 194 ++++++++++++++++++ .../libraries/LibC/include/bits/getopt.h | 15 ++ userspace/libraries/LibC/include/getopt.h | 27 +++ userspace/libraries/LibC/include/unistd.h | 6 +- userspace/libraries/LibC/unistd.cpp | 85 -------- 6 files changed, 239 insertions(+), 89 deletions(-) create mode 100644 userspace/libraries/LibC/getopt.cpp create mode 100644 userspace/libraries/LibC/include/bits/getopt.h create mode 100644 userspace/libraries/LibC/include/getopt.h diff --git a/userspace/libraries/LibC/CMakeLists.txt b/userspace/libraries/LibC/CMakeLists.txt index d268dfc1..85cd0ade 100644 --- a/userspace/libraries/LibC/CMakeLists.txt +++ b/userspace/libraries/LibC/CMakeLists.txt @@ -12,6 +12,7 @@ set(LIBC_SOURCES fenv.cpp fnmatch.cpp ftw.cpp + getopt.cpp grp.cpp ifaddrs.cpp inttypes.cpp diff --git a/userspace/libraries/LibC/getopt.cpp b/userspace/libraries/LibC/getopt.cpp new file mode 100644 index 00000000..a2965e7f --- /dev/null +++ b/userspace/libraries/LibC/getopt.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include + +char* optarg = nullptr; +int opterr = 1; +int optind = 1; +int optopt = 0; + +static int s_idx_in_arg = -1; +static int s_old_optind = 1; + +static int getopt_long_impl(int argc, char* const argv[], const char* optstring, const struct option* longopts, int* longindex, bool need_double_hyphen) +{ + if (optind >= argc) + return -1; + + if (optind == 0) + { + s_old_optind = -1; + s_idx_in_arg = -1; + optind = 1; + } + + const char* curr = argv[optind]; + if (curr == nullptr || curr[0] != '-' || curr[1] == '\0') + return -1; + + if (curr[1] == '-' && curr[2] == '\0') + { + optind++; + return -1; + } + + if (s_old_optind != optind) + s_idx_in_arg = -1; + struct dummy { ~dummy() { s_old_optind = optind; }} _; + + if (s_idx_in_arg == -1 && (!need_double_hyphen || curr[1] == '-')) + { + for (size_t i = 0; longopts[i].name; i++) + { + const auto& opt = longopts[i]; + const size_t name_len = strlen(opt.name); + if (strncmp(curr + 2, opt.name, name_len) != 0) + continue; + if (curr[2 + name_len] != '=' && curr[2 + name_len] != '\0') + continue; + + bool has_argument; + switch (opt.has_arg) + { + case no_argument: + has_argument = false; + break; + case required_argument: + has_argument = true; + break; + case optional_argument: + has_argument = (curr[2 + name_len] == '=') + || (optind + 1 < argc && argv[optind + 1][0] != '-'); + break; + default: + assert(false); + } + + if (!has_argument) + { + if (curr[2 + name_len] == '=') + { + if (opterr && optstring[0] != ':') + fprintf(stderr, "%s: option takes no argument -- %.*s\n", argv[0], static_cast(name_len), curr + 2); + optind++; + return (optstring[0] == ':') ? ':' : '?'; + } + optarg = nullptr; + optind++; + } + else + { + if (curr[2 + name_len] == '=') + { + optarg = const_cast(curr + 2 + name_len + 1); + optind++; + } + else + { + if (optind + 1 >= argc) + { + if (opterr && optstring[0] != ':') + fprintf(stderr, "%s: option requires an argument -- %.*s\n", argv[0], static_cast(name_len), curr + 2); + optind++; + return (optstring[0] == ':') ? ':' : '?'; + } + optarg = argv[optind + 1]; + optind += 2; + } + } + + if (longindex != nullptr) + *longindex = i; + + if (opt.flag == nullptr) + return opt.val; + *opt.flag = opt.val; + return 0; + } + + if (curr[1] == '-') + { + if (opterr && optstring[0] != ':') + fprintf(stderr, "%s: illegal option -- %s\n", argv[0], curr + 2); + optind++; + return '?'; + } + } + + if (s_idx_in_arg == -1) + s_idx_in_arg = 1; + + for (size_t i = 0; optstring[i]; i++) + { + if (optstring[i] == ':') + continue; + if (curr[s_idx_in_arg] != optstring[i]) + continue; + + const bool has_argument = (optstring[i + 1] == ':'); + if (!has_argument) + { + s_idx_in_arg++; + if (curr[s_idx_in_arg] == '\0') + { + s_idx_in_arg = -1; + optind++; + } + } + else + { + if (curr[s_idx_in_arg + 1] != '\0') + { + optarg = const_cast(curr + s_idx_in_arg + 1); + optind++; + s_idx_in_arg = -1; + } + else + { + if (optind + 1 >= argc) + { + if (opterr && optstring[0] != ':') + fprintf(stderr, "%s: option requires an argument -- %c\n", argv[0], optstring[i]); + optopt = optstring[i]; + optind++; + return optstring[0] == ':' ? ':' : '?'; + } + optarg = const_cast(argv[optind + 1]); + optind += 2; + s_idx_in_arg = -1; + } + } + + return optstring[i]; + } + + if (opterr && optstring[0] != ':') + fprintf(stderr, "%s: illegal option -- %c\n", argv[0], curr[s_idx_in_arg]); + optopt = curr[s_idx_in_arg]; + + s_idx_in_arg++; + if (curr[s_idx_in_arg] == '\0') + { + s_idx_in_arg = -1; + optind++; + } + + return '?'; +} + +int getopt(int argc, char* const argv[], const char* optstring) +{ + struct option option {}; + return getopt_long_impl(argc, argv, optstring, &option, nullptr, true); +} + +int getopt_long(int argc, char* argv[], const char* optstring, const struct option* longopts, int* longindex) +{ + return getopt_long_impl(argc, argv, optstring, longopts, longindex, true); +} + +int getopt_long_only(int argc, char* argv[], const char* optstring, const struct option* longopts, int* longindex) +{ + return getopt_long_impl(argc, argv, optstring, longopts, longindex, false); +} diff --git a/userspace/libraries/LibC/include/bits/getopt.h b/userspace/libraries/LibC/include/bits/getopt.h new file mode 100644 index 00000000..84b0fe10 --- /dev/null +++ b/userspace/libraries/LibC/include/bits/getopt.h @@ -0,0 +1,15 @@ +#ifndef _BITS_GETOPT_H +#define _BITS_GETOPT_H 1 + +#include + +__BEGIN_DECLS + +int getopt(int argc, char* const argv[], const char* optstring); + +extern char* optarg; +extern int opterr, optind, optopt; + +__END_DECLS + +#endif diff --git a/userspace/libraries/LibC/include/getopt.h b/userspace/libraries/LibC/include/getopt.h new file mode 100644 index 00000000..8ab0390b --- /dev/null +++ b/userspace/libraries/LibC/include/getopt.h @@ -0,0 +1,27 @@ +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#include + +__BEGIN_DECLS + +#include + +struct option +{ + const char* name; + int has_arg; + int* flag; + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int getopt_long(int argc, char* argv[], const char* optstring, const struct option* longopts, int* longindex); +int getopt_long_only(int argc, char* argv[], const char* optstring, const struct option* longopts, int* longindex); + +__END_DECLS + +#endif diff --git a/userspace/libraries/LibC/include/unistd.h b/userspace/libraries/LibC/include/unistd.h index 33e18e64..46b6dfd1 100644 --- a/userspace/libraries/LibC/include/unistd.h +++ b/userspace/libraries/LibC/include/unistd.h @@ -124,6 +124,8 @@ __BEGIN_DECLS #define __need_useconds_t #include +#include + enum { _CS_PATH = 1, @@ -547,7 +549,6 @@ long gethostid(void); int gethostname(char* name, size_t namelen); char* getlogin(void); int getlogin_r(char* name, size_t namesize); -int getopt(int argc, char* const argv[], const char* optstring); pid_t getpgid(pid_t pid); pid_t getpgrp(void); pid_t getpid(void); @@ -600,9 +601,6 @@ int chroot(const char* path); int getpagesize(void); char* getpass(const char* prompt); -extern char* optarg; -extern int opterr, optind, optopt; - long syscall(long syscall, ...); __END_DECLS diff --git a/userspace/libraries/LibC/unistd.cpp b/userspace/libraries/LibC/unistd.cpp index 3ba2b00a..73df2a29 100644 --- a/userspace/libraries/LibC/unistd.cpp +++ b/userspace/libraries/LibC/unistd.cpp @@ -541,91 +541,6 @@ int rmdir(const char* path) return unlinkat(AT_FDCWD, path, AT_REMOVEDIR); } -char* optarg = nullptr; -int opterr = 1; -int optind = 1; -int optopt = 0; - -int getopt(int argc, char* const argv[], const char* optstring) -{ - if (optind >= argc) - return -1; - - static int idx = 1; - const char* current = argv[optind]; - - // if "--" is encountered, no more options are parsed - if (idx == -1) - return -1; - - // if current is nullptr, does not start with '-' or is string "-", return -1 - if (current == nullptr || current[0] != '-' || current[1] == '\0') - return -1; - - // if current points to string "--" increment optind and return -1 - if (current[1] == '-' && current[2] == '\0') - { - idx = -1; - optind++; - return -1; - } - - for (size_t i = 0; optstring[i]; i++) - { - if (optstring[i] == ':') - continue; - if (current[idx] != optstring[i]) - continue; - - if (optstring[i + 1] == ':') - { - if (current[idx + 1]) - { - optarg = const_cast(current + idx + 1); - optind += 1; - } - else - { - optarg = const_cast(argv[optind + 1]); - optind += 2; - } - - idx = 1; - - if (optind > argc) - { - if (opterr && optstring[0] != ':') - fprintf(stderr, "%s: option requires an argument -- %c\n", argv[0], optstring[i]); - optopt = optstring[i]; - return optstring[0] == ':' ? ':' : '?'; - } - - return optstring[i]; - } - else - { - if (current[++idx] == '\0') - { - idx = 1; - optind++; - } - - return optstring[i]; - } - } - - if (opterr && optstring[0] != ':') - fprintf(stderr, "%s: illegal option -- %c\n", argv[0], current[idx]); - - if (current[++idx] == '\0') - { - idx = 1; - optind++; - } - - return '?'; -} - int chroot(const char* path) { return syscall(SYS_CHROOT, path);