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

source code of libc/src/unistd/getopt.cpp