207 lines · cpp
1//===-- Implementation of getopt ------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "src/unistd/getopt.h"10#include "src/__support/CPP/optional.h"11#include "src/__support/CPP/string_view.h"12#include "src/__support/File/file.h"13#include "src/__support/common.h"14#include "src/__support/macros/config.h"15#include "src/stdio/fprintf.h"16 17#include "hdr/types/FILE.h"18 19// This is POSIX compliant and does not support GNU extensions, mainly this is20// just the re-ordering of argv elements such that unknown arguments can be21// easily iterated over.22 23namespace LIBC_NAMESPACE_DECL {24 25template <typename T> struct RefWrapper {26 RefWrapper() = delete;27 constexpr RefWrapper(T *p) : ptr{p} {}28 constexpr RefWrapper(const RefWrapper &) = default;29 RefWrapper &operator=(const RefWrapper &) = default;30 operator T &() { return *ptr; }31 T &get() { return *ptr; }32 T *ptr;33};34 35struct GetoptContext {36 RefWrapper<char *> optarg;37 RefWrapper<int> optind;38 RefWrapper<int> optopt;39 RefWrapper<unsigned> optpos;40 41 RefWrapper<int> opterr;42 43 FILE *errstream;44 45 GetoptContext &operator=(const GetoptContext &) = default;46 47 template <typename... Ts> void report_error(const char *fmt, Ts... ts) {48 if (opterr)49 LIBC_NAMESPACE::fprintf(50 errstream ? errstream51 : reinterpret_cast<FILE *>(LIBC_NAMESPACE::stderr),52 fmt, ts...);53 }54};55 56struct OptstringParser {57 using value_type = struct {58 char c;59 bool arg;60 };61 62 cpp::string_view optstring;63 64 struct iterator {65 cpp::string_view curr;66 67 iterator operator++() {68 curr = curr.substr(1);69 return *this;70 }71 72 bool operator!=(iterator other) { return curr.data() != other.curr.data(); }73 74 value_type operator*() {75 value_type r{curr.front(), false};76 if (!curr.substr(1).empty() && curr.substr(1).front() == ':') {77 this->operator++();78 r.arg = true;79 }80 return r;81 }82 };83 84 iterator begin() {85 bool skip = optstring.front() == '-' || optstring.front() == '+' ||86 optstring.front() == ':';87 return {optstring.substr(!!skip)};88 }89 90 iterator end() { return {optstring.substr(optstring.size())}; }91};92 93int getopt_r(int argc, char *const argv[], const char *optstring,94 GetoptContext &ctx) {95 auto failure = [&ctx](int ret = -1) {96 ctx.optpos.get() = 0;97 return ret;98 };99 100 if (ctx.optind >= argc || !argv[ctx.optind])101 return failure();102 103 cpp::string_view current =104 cpp::string_view{argv[ctx.optind]}.substr(ctx.optpos);105 106 auto move_forward = [¤t, &ctx] {107 current = current.substr(1);108 ctx.optpos.get()++;109 };110 111 // If optpos is nonzero, then we are already parsing a valid flag and these112 // need not be checked.113 if (ctx.optpos == 0) {114 if (current[0] != '-')115 return failure();116 117 if (current == "--") {118 ctx.optind.get()++;119 return failure();120 }121 122 // Eat the '-' char.123 move_forward();124 if (current.empty())125 return failure();126 }127 128 auto find_match =129 [current, optstring]() -> cpp::optional<OptstringParser::value_type> {130 for (auto i : OptstringParser{optstring})131 if (i.c == current[0])132 return i;133 return {};134 };135 136 auto match = find_match();137 if (!match) {138 ctx.report_error("%s: illegal option -- %c\n", argv[0], current[0]);139 ctx.optopt.get() = current[0];140 return failure('?');141 }142 143 // We've matched so eat that character.144 move_forward();145 if (match->arg) {146 // If we found an option that takes an argument and our current is not over,147 // the rest of current is that argument. Ie, "-cabc" with opstring "c:",148 // then optarg should point to "abc". Otherwise the argument to c will be in149 // the next arg like "-c abc".150 if (!current.empty()) {151 // This const cast is fine because current was already holding a mutable152 // string, it just doesn't have the semantics to note that, we could use153 // span but it doesn't have string_view string niceties.154 ctx.optarg.get() = const_cast<char *>(current.data());155 } else {156 // One char lookahead to see if we ran out of arguments. If so, return ':'157 // if the first character of optstring is ':'. optind must stay at the158 // current value so only increase it after we known there is another arg.159 if (ctx.optind + 1 >= argc || !argv[ctx.optind + 1]) {160 ctx.report_error("%s: option requires an argument -- %c\n", argv[0],161 match->c);162 return failure(optstring[0] == ':' ? ':' : '?');163 }164 ctx.optarg.get() = argv[++ctx.optind];165 }166 ctx.optind++;167 ctx.optpos.get() = 0;168 } else if (current.empty()) {169 // If this argument is now empty we are safe to move onto the next one.170 ctx.optind++;171 ctx.optpos.get() = 0;172 }173 174 return match->c;175}176 177namespace impl {178 179extern "C" {180char *optarg = nullptr;181int optind = 1;182int optopt = 0;183int opterr = 0;184}185 186static unsigned optpos;187 188static GetoptContext ctx{&impl::optarg, &impl::optind, &impl::optopt,189 &optpos, &impl::opterr, /*errstream=*/nullptr};190 191#ifndef LIBC_COPT_PUBLIC_PACKAGING192// This is used exclusively in tests.193void set_getopt_state(char **optarg, int *optind, int *optopt, unsigned *optpos,194 int *opterr, FILE *errstream) {195 ctx = {optarg, optind, optopt, optpos, opterr, errstream};196}197#endif198 199} // namespace impl200 201LLVM_LIBC_FUNCTION(int, getopt,202 (int argc, char *const argv[], const char *optstring)) {203 return getopt_r(argc, argv, optstring, impl::ctx);204}205 206} // namespace LIBC_NAMESPACE_DECL207