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; 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 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 47 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 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 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 = [¤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 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. 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