Index: libc/config/linux/aarch64/entrypoints.txt =================================================================== --- libc/config/linux/aarch64/entrypoints.txt +++ libc/config/linux/aarch64/entrypoints.txt @@ -129,6 +129,7 @@ libc.src.unistd.unlink libc.src.unistd.unlinkat libc.src.unistd.write + libc.src.unistd.getopt ) set(TARGET_LIBM_ENTRYPOINTS Index: libc/config/linux/x86_64/entrypoints.txt =================================================================== --- libc/config/linux/x86_64/entrypoints.txt +++ libc/config/linux/x86_64/entrypoints.txt @@ -129,6 +129,7 @@ libc.src.unistd.unlink libc.src.unistd.unlinkat libc.src.unistd.write + libc.src.unistd.getopt ) set(TARGET_LIBM_ENTRYPOINTS Index: libc/spec/posix.td =================================================================== --- libc/spec/posix.td +++ libc/spec/posix.td @@ -30,6 +30,7 @@ RestrictedPtrType CharRestrictedDoublePtr = RestrictedPtrType; ConstType ConstCharPtr = ConstType; ConstType ConstRestrictedCharPtr = ConstType; + PtrType CharPtrPtr = PtrType; NamedType ModeTType = NamedType<"mode_t">; @@ -351,6 +352,16 @@ RetValSpec, [ArgSpec, ArgSpec, ArgSpec] >, + FunctionSpec< + "getopt", + RetValSpec, + // Note, CharPtrPtr is wrong. The type should be "char *const []". + // We could reasonably use "char * const *", but I couldn't + // manage to express this, const would always go to the back, + // like "const char **". I might be doing something wrong, + // or I will need to change hdr-gen, but this can come later. + [ArgSpec, ArgSpec, ArgSpec] + >, ] >; Index: libc/src/unistd/CMakeLists.txt =================================================================== --- libc/src/unistd/CMakeLists.txt +++ libc/src/unistd/CMakeLists.txt @@ -127,3 +127,15 @@ DEPENDS .${LIBC_TARGET_OS}.write ) + +add_entrypoint_object( + getopt + SRCS + getopt.cpp + HDRS + getopt.h + DEPENDS + libc.include.unistd + libc.src.__support.CPP.optional + libc.src.__support.CPP.string_view +) Index: libc/src/unistd/getopt.h =================================================================== --- /dev/null +++ libc/src/unistd/getopt.h @@ -0,0 +1,25 @@ +//===-- Implementation header for getopt ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_UNISTD_GETOPT_H +#define LLVM_LIBC_SRC_UNISTD_GETOPT_H + +#include + +namespace __llvm_libc { + +extern char *optarg; +extern int optind; +extern int optopt; +extern int opterr; + +int getopt(int argc, char *const argv[], const char *optstring); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_GETOPT_H Index: libc/src/unistd/getopt.cpp =================================================================== --- /dev/null +++ libc/src/unistd/getopt.cpp @@ -0,0 +1,156 @@ +//===-- Implementation of getopt ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/unistd/getopt.h" +#include "src/__support/CPP/optional.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/common.h" + +namespace __llvm_libc { + +struct GetoptState { + char *&optarg; + int &optind; + int &optopt; + unsigned &optpos; + + int opterr; +}; + +struct OptstringParser { + using value_type = struct { + char c; + bool arg; + }; + + cpp::string_view optstring; + + struct iterator { + cpp::string_view curr; + + iterator operator++() { + curr = curr.substr(1); + return *this; + } + + bool operator!=(iterator other) { return curr.data() != other.curr.data(); } + + value_type operator*() { + value_type r{curr.front(), false}; + if (!curr.substr(1).empty() && curr.substr(1).front() == ':') { + this->operator++(); + r.arg = true; + } + return r; + } + }; + + iterator begin() { + bool skip = optstring.front() == '-' || optstring.front() == '+' || + optstring.front() == ':'; + return {optstring.substr(!!skip)}; + } + + iterator end() { return {optstring.substr(optstring.size())}; } +}; + +int getopt_r(int argc, char *const argv[], const char *optstring, + GetoptState &state) { + auto failure = [state](int ret = -1) { + state.optpos = 0; + return ret; + }; + + if (state.optind >= argc || !argv[state.optind]) + return failure(); + + cpp::string_view current = + cpp::string_view{argv[state.optind]}.substr(state.optpos); + + auto move_forward = [¤t, state] { + current = current.substr(1); + state.optpos++; + }; + + // If optpos is nonzero, then we are already parsing a valid flag and these + // need not be checked. + if (state.optpos == 0) { + if (current[0] != '-') + return failure(); + + if (current == "--") { + state.optind++; + return failure(); + } + + // Eat the '-' char. + move_forward(); + if (current.empty()) + return failure(); + } + + auto find_match = + [current, optstring]() -> cpp::optional { + for (auto i : OptstringParser{optstring}) + if (i.c == current[0]) + return i; + return {}; + }; + + auto match = find_match(); + if (!match) { + // TODO: check state.opterr and report if it is set. This is non-trivial + // because it needs some stdio bits. + state.optopt = current[0]; + return failure('?'); + } + + // We've matched so eat that character. + move_forward(); + if (match->arg) { + // If we found an option that takes an argument and our current is not over, + // the rest of current is that argument. Ie, "-cabc" with opstring "c:", + // then optarg should point to "abc". Otherwise the argument to c will be in + // the next arg like "-c abc". + if (!current.empty()) { + // This const cast is fine because current was already holding a mutable + // string, it just doesn't have the semantics to note that, we could use + // span but it doesn't have string_view string niceties. + optarg = const_cast(current.data()); + } else { + // We ran out of arguments. Return ':' if the first character of optstring + // is ':'. + if (state.optind + 1 >= argc || !argv[state.optind + 1]) + return failure(optstring[0] == ':' ? ':' : '?'); + state.optarg = argv[++state.optind]; + } + state.optind++; + state.optpos = 0; + } else if (current.empty()) { + // If this argument is now empty we are save to move onto the next one. + state.optind++; + state.optpos = 0; + } + + return match->c; +} + +char *optarg; +int optind = 1; +int optopt; +int opterr; + +unsigned optpos; + +LLVM_LIBC_FUNCTION(int, getopt, + (int argc, char *const argv[], const char *optstring)) { + GetoptState state{optarg, optind, optopt, optpos, opterr}; + return getopt_r(argc, argv, optstring, state); +} + +} // namespace __llvm_libc Index: libc/test/src/unistd/CMakeLists.txt =================================================================== --- libc/test/src/unistd/CMakeLists.txt +++ libc/test/src/unistd/CMakeLists.txt @@ -233,3 +233,14 @@ libc.src.unistd.close libc.src.unistd.unlinkat ) + +add_libc_unittest( + getopt_test + SUITE + libc_unistd_unittests + SRCS + getopt_test.cpp + DEPENDS + libc.src.unistd.getopt + libc.src.__support.CPP.array +) Index: libc/test/src/unistd/getopt_test.cpp =================================================================== --- /dev/null +++ libc/test/src/unistd/getopt_test.cpp @@ -0,0 +1,94 @@ +//===-- Unittests for getopt ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/unistd/getopt.h" +#include "utils/UnitTest/Test.h" + +#include "src/__support/CPP/array.h" + +using __llvm_libc::cpp::array; + +struct LlvmLibcGetoptTest : public __llvm_libc::testing::Test { + void SetUp() override { ASSERT_EQ(__llvm_libc::optind, 1); } + + void TearDown() override { __llvm_libc::optind = 1; } +}; + +// This is safe because getopt doesn't currently permute argv like GNU's getopt +// does so this just helps silence warnings. +char *operator"" _c(const char *c, size_t) { return const_cast(c); } + +TEST_F(LlvmLibcGetoptTest, NoMatch) { + array argv{"prog"_c, "arg1"_c, nullptr}; + + // optind >= argc + EXPECT_EQ(__llvm_libc::getopt(1, argv.data(), "..."), -1); + + // argv[optind] == nullptr + __llvm_libc::optind = 2; + EXPECT_EQ(__llvm_libc::getopt(100, argv.data(), "..."), -1); + + // argv[optind][0] != '-' + __llvm_libc::optind = 1; + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "a"), -1); + ASSERT_EQ(__llvm_libc::optind, 1); + + // argv[optind] == "-" + argv[1] = "-"_c; + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "a"), -1); + ASSERT_EQ(__llvm_libc::optind, 1); + + // argv[optind] == "--", then return -1 and incremement optind + argv[1] = "--"_c; + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "a"), -1); + EXPECT_EQ(__llvm_libc::optind, 2); +} + +TEST_F(LlvmLibcGetoptTest, WrongMatch) { + array argv{"prog"_c, "-b"_c, nullptr}; + + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "a"), (int)'?'); + EXPECT_EQ(__llvm_libc::optopt, (int)'b'); + EXPECT_EQ(__llvm_libc::optind, 1); +} + +TEST_F(LlvmLibcGetoptTest, MissingArg) { + array argv{"prog"_c, "-b"_c, nullptr}; + + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), ":b:"), (int)':'); + ASSERT_EQ(__llvm_libc::optind, 1); + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "b:"), (int)'?'); + EXPECT_EQ(__llvm_libc::optind, 1); +} + +TEST_F(LlvmLibcGetoptTest, ParseArgInCurrent) { + array argv{"prog"_c, "-barg"_c, nullptr}; + + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "b:"), (int)'b'); + EXPECT_STREQ(__llvm_libc::optarg, "arg"); + EXPECT_EQ(__llvm_libc::optind, 2); +} + +TEST_F(LlvmLibcGetoptTest, ParseArgInNext) { + array argv{"prog"_c, "-b"_c, "arg"_c, nullptr}; + + EXPECT_EQ(__llvm_libc::getopt(3, argv.data(), "b:"), (int)'b'); + EXPECT_STREQ(__llvm_libc::optarg, "arg"); + EXPECT_EQ(__llvm_libc::optind, 3); +} + +TEST_F(LlvmLibcGetoptTest, ParseMutliInOne) { + array argv{"prog"_c, "-abc"_c, nullptr}; + + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "abc"), (int)'a'); + ASSERT_EQ(__llvm_libc::optind, 1); + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "abc"), (int)'b'); + ASSERT_EQ(__llvm_libc::optind, 1); + EXPECT_EQ(__llvm_libc::getopt(2, argv.data(), "abc"), (int)'c'); + EXPECT_EQ(__llvm_libc::optind, 2); +}