xref: /aosp_15_r20/external/llvm-libc/src/unistd/getopt.cpp (revision 71db0c75aadcf003ffe3238005f61d7618a3fead)
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-exception
6 //
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 is
20 // just the re-ordering of argv elements such that unknown arguments can be
21 // easily iterated over.
22 
23 namespace LIBC_NAMESPACE_DECL {
24 
25 template <typename T> struct RefWrapper {
26   RefWrapper() = delete;
RefWrapperLIBC_NAMESPACE_DECL::RefWrapper27   constexpr RefWrapper(T *p) : ptr{p} {}
28   constexpr RefWrapper(const RefWrapper &) = default;
29   RefWrapper &operator=(const RefWrapper &) = default;
operator T&LIBC_NAMESPACE_DECL::RefWrapper30   operator T &() { return *ptr; }
getLIBC_NAMESPACE_DECL::RefWrapper31   T &get() { return *ptr; }
32   T *ptr;
33 };
34 
35 struct 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 
report_errorLIBC_NAMESPACE_DECL::GetoptContext47   template <typename... Ts> void report_error(const char *fmt, Ts... ts) {
48     if (opterr)
49       LIBC_NAMESPACE::fprintf(
50           errstream ? errstream
51                     : reinterpret_cast<FILE *>(LIBC_NAMESPACE::stderr),
52           fmt, ts...);
53   }
54 };
55 
56 struct 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 
operator ++LIBC_NAMESPACE_DECL::OptstringParser::iterator67     iterator operator++() {
68       curr = curr.substr(1);
69       return *this;
70     }
71 
operator !=LIBC_NAMESPACE_DECL::OptstringParser::iterator72     bool operator!=(iterator other) { return curr.data() != other.curr.data(); }
73 
operator *LIBC_NAMESPACE_DECL::OptstringParser::iterator74     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 
beginLIBC_NAMESPACE_DECL::OptstringParser84   iterator begin() {
85     bool skip = optstring.front() == '-' || optstring.front() == '+' ||
86                 optstring.front() == ':';
87     return {optstring.substr(!!skip)};
88   }
89 
endLIBC_NAMESPACE_DECL::OptstringParser90   iterator end() { return {optstring.substr(optstring.size())}; }
91 };
92 
getopt_r(int argc,char * const argv[],const char * optstring,GetoptContext & ctx)93 int 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 = [&current, &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 these
112   // 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 in
149     // the next arg like "-c abc".
150     if (!current.empty()) {
151       // This const cast is fine because current was already holding a mutable
152       // string, it just doesn't have the semantics to note that, we could use
153       // 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 the
158       // 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 
177 namespace impl {
178 
179 extern "C" {
180 char *optarg = nullptr;
181 int optind = 1;
182 int optopt = 0;
183 int opterr = 0;
184 }
185 
186 static unsigned optpos;
187 
188 static GetoptContext ctx{&impl::optarg, &impl::optind, &impl::optopt,
189                          &optpos,       &impl::opterr, /*errstream=*/nullptr};
190 
191 #ifndef LIBC_COPT_PUBLIC_PACKAGING
192 // This is used exclusively in tests.
set_getopt_state(char ** optarg,int * optind,int * optopt,unsigned * optpos,int * opterr,FILE * errstream)193 void 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 #endif
198 
199 } // namespace impl
200 
201 LLVM_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_DECL
207