From 725ad5649c237bd62729f4edf2dea3664b20f02a Mon Sep 17 00:00:00 2001 From: ArjenS-LA Date: Thu, 15 Oct 2026 02:54:53 -0700 Subject: [PATCH] feat(libc): implement getopt_long_only --- core/libs/prx/libSceLibcInternal/Getopt.cpp | 36 ++++++++--- core/libs/tests/GuestGetopt.cpp | 67 +++++++++++++++++++++ 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceLibcInternal/Getopt.cpp b/core/libs/prx/libSceLibcInternal/Getopt.cpp index 17062904ff..80645072b4 100644 --- a/core/libs/prx/libSceLibcInternal/Getopt.cpp +++ b/core/libs/prx/libSceLibcInternal/Getopt.cpp @@ -38,6 +38,7 @@ constexpr int OptionalArgument = 2; constexpr int FlagPermute = 1; constexpr int FlagAllArguments = 2; +constexpr int FlagLongOnly = 4; constexpr int BadCharacter = '?'; constexpr int InOrder = 1; @@ -112,7 +113,7 @@ void PermuteArguments(int nonOptionStart, int nonOptionEnd, int optionEnd, char* } } -int ParseLongOptions(char* const* arguments, const char* options, const GuestOption* longOptions, int* index, const char* dash) { +int ParseLongOptions(char* const* arguments, const char* options, const GuestOption* longOptions, int* index, const char* dash, bool shortToo, int flags) { char* const current = g_longPlace; int match = -1; bool exactMatch = false; @@ -136,8 +137,9 @@ int ParseLongOptions(char* const* arguments, const char* options, const GuestOpt exactMatch = true; break; } + if (shortToo && length == 1) continue; if (match == -1) match = i; - else if (longOptions[i].hasArgument != longOptions[match].hasArgument || + else if ((flags & FlagLongOnly) || longOptions[i].hasArgument != longOptions[match].hasArgument || longOptions[i].flag != longOptions[match].flag || longOptions[i].value != longOptions[match].value) secondPartialMatch = true; @@ -149,6 +151,10 @@ int ParseLongOptions(char* const* arguments, const char* options, const GuestOpt return BadCharacter; } if (match == -1) { + if (shortToo) { + --optind_nid_postfix; + return -1; + } if (PrintError(options)) Warn(std::string("unrecognized option `") + dash + current + "'"); optopt_nid_postfix = 0; return BadCharacter; @@ -237,11 +243,21 @@ int GetoptInternal(int count, char* const* arguments, const char* options, const break; } - if (longOptions && *g_longPlace == '-' && g_longPlace != arguments[optind_nid_postfix]) { - ++g_longPlace; - const int result = ParseLongOptions(arguments, options, longOptions, index, "--"); - g_longPlace = g_emptyMessage; - return result; + if (longOptions && g_longPlace != arguments[optind_nid_postfix] && + (*g_longPlace == '-' || (flags & FlagLongOnly))) { + bool shortToo = false; + const char* dash = "-"; + if (*g_longPlace == '-') { + ++g_longPlace; + dash = "--"; + } else if (*g_longPlace != ':' && std::strchr(options, *g_longPlace)) { + shortToo = true; + } + const int result = ParseLongOptions(arguments, options, longOptions, index, dash, shortToo, flags); + if (result != -1) { + g_longPlace = g_emptyMessage; + return result; + } } const int character = OptionCharacter(g_longPlace++); @@ -265,7 +281,7 @@ int GetoptInternal(int count, char* const* arguments, const char* options, const } g_longPlace = Argument(arguments, optind_nid_postfix, "getopt_long"); } - const int result = ParseLongOptions(arguments, options, longOptions, index, "-W "); + const int result = ParseLongOptions(arguments, options, longOptions, index, "-W ", false, flags); g_longPlace = g_emptyMessage; return result; } @@ -355,4 +371,8 @@ int APS5_VABI getopt_long_nid_postfix(int count, char* const* arguments, const c return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute); } +int APS5_VABI getopt_long_only_nid_postfix(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index) { + return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute | FlagLongOnly); +} + } diff --git a/core/libs/tests/GuestGetopt.cpp b/core/libs/tests/GuestGetopt.cpp index fa3fbebf1b..26e0ca0803 100644 --- a/core/libs/tests/GuestGetopt.cpp +++ b/core/libs/tests/GuestGetopt.cpp @@ -17,6 +17,7 @@ struct GuestOption { extern "C" { int APS5_VABI getopt_nid_postfix(int, char* const*, const char*); int APS5_VABI getopt_long_nid_postfix(int, char* const*, const char*, const GuestOption*, int*); +int APS5_VABI getopt_long_only_nid_postfix(int, char* const*, const char*, const GuestOption*, int*); int APS5_VABI setenv_nid_postfix(const char*, const char*, int); int APS5_VABI unsetenv_nid_postfix(const char*); extern int opterr_nid_postfix; @@ -244,6 +245,71 @@ void ShortOptions() { ExpectText(RunGetopt("bc", permuted), "|3", "getopt does not permute"); } +void LongOnlyOptions() { + int flag = 0; + const GuestOption options[] = { + {"alpha", 0, nullptr, 100}, + {"alpine", 0, nullptr, 100}, + {"b", 0, nullptr, 101}, + {"required", 1, nullptr, 102}, + {"optional", 2, nullptr, 103}, + {"flag", 0, &flag, 7}, + {nullptr, 0, nullptr, 0}, + }; + struct Case { + const char* arguments; + const char* shortOptions; + std::vector results; + std::vector values; + int finalIndex; + }; + const Case cases[] = { + {"foo -alpha --alpha -b", "ab", {100, 100, 101}, {"", "", ""}, 4}, + {"foo -a -acvalue", "ac:", {'a', 'a', 'c'}, {"", "", "value"}, 3}, + {"foo -required=one -req two -optional=three -optional four", "", {102, 102, 103, 103}, {"one", "two", "three", ""}, 6}, + {"foo operand -alpha tail -b", "ab", {100, 101}, {"", ""}, 3}, + {"foo -al --al", "a", {'?', '?'}, {"", ""}, 3}, + {"foo -unknown", "", {'?'}, {""}, 2}, + {"foo -required", ":", {':'}, {""}, 2}, + {"foo -alpha=yes", "", {'?'}, {""}, 2}, + {"foo -flag", "", {0}, {""}, 2}, + {"foo -- -alpha", "", {}, {}, 2}, + {"foo operand -alpha", "+", {}, {}, 1}, + {"foo operand -alpha", "-", {1, 100}, {"operand", ""}, 3}, + {"foo -W alpha", "W;", {100}, {""}, 3}, + }; + for (const auto& test : cases) { + ArgumentVector arguments(test.arguments); + Reset(); + opterr_nid_postfix = 0; + for (std::size_t i = 0; i < test.results.size(); ++i) { + const int result = getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), test.shortOptions, options, nullptr); + Expect(result == test.results[i], std::string("getopt_long_only result: ") + test.arguments); + ExpectText(optarg_nid_postfix ? optarg_nid_postfix : "", test.values[i], "getopt_long_only argument"); + } + Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), test.shortOptions, options, nullptr) == -1, + std::string("getopt_long_only termination: ") + test.arguments); + Expect(optind_nid_postfix == test.finalIndex, std::string("getopt_long_only optind: ") + test.arguments); + } + Expect(flag == 7, "getopt_long_only flag pointer"); + { + ArgumentVector arguments("foo -al"); + StderrCapture capture; + Reset(); + int index = -1; + Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, &index) == '?' && + optopt_nid_postfix == 0 && index == -1, "ambiguous aliases keep index unchanged"); + ExpectText(capture.Text(), "eboot.bin: option `-al' is ambiguous\n", "single dash diagnostic"); + } + ArgumentVector arguments("foo -alpha"); + Reset(); + int index = -1; + Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, &index) == 100 && index == 0, + "long-only index"); + optind_nid_postfix = 0; + Expect(getopt_long_only_nid_postfix(arguments.Count(), arguments.Values(), "a", options, nullptr) == 100, "long-only restart"); +} + void LongOptions() { int flag = 0; const GuestOption options[] = { @@ -364,4 +430,5 @@ int main() { FreeBsdGetoptLongCases(); ShortOptions(); LongOptions(); + LongOnlyOptions(); }