1/* vi: set sw=4 ts=4: */ 2/* 3 * universal getopt32 implementation for busybox 4 * 5 * Copyright (C) 2003-2005 Vladimir Oleynik <dzo@simtreas.ru> 6 * 7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. 8 */ 9 10#include <getopt.h> 11#include "libbb.h" 12 13 14/* Code here assumes that 'unsigned' is at least 32 bits wide */ 15 16const char *opt_complementary; 17 18typedef struct { 19 int opt; 20 int list_flg; 21 unsigned switch_on; 22 unsigned switch_off; 23 unsigned incongruously; 24 unsigned requires; 25 void **optarg; /* char **optarg or llist_t **optarg */ 26 int *counter; 27} t_complementary; 28 29/* You can set applet_long_options for parse called long options */ 30#if ENABLE_GETOPT_LONG 31static const struct option bb_null_long_options[1] = { 32 { 0, 0, 0, 0 } 33}; 34const char *applet_long_options; 35#endif 36 37uint32_t option_mask32; 38 39uint32_t 40getopt32(char **argv, const char *applet_opts, ...) 41{ 42 int argc; 43 unsigned flags = 0; 44 unsigned requires = 0; 45 t_complementary complementary[33]; 46 int c; 47 const unsigned char *s; 48 t_complementary *on_off; 49 va_list p; 50#if ENABLE_GETOPT_LONG 51 const struct option *l_o; 52 struct option *long_options = (struct option *) &bb_null_long_options; 53#endif 54 unsigned trigger; 55 char **pargv = NULL; 56 int min_arg = 0; 57 int max_arg = -1; 58 59#define SHOW_USAGE_IF_ERROR 1 60#define ALL_ARGV_IS_OPTS 2 61#define FIRST_ARGV_IS_OPT 4 62#define FREE_FIRST_ARGV_IS_OPT 8 63 int spec_flgs = 0; 64 65 argc = 0; 66 while (argv[argc]) 67 argc++; 68 69 va_start(p, applet_opts); 70 71 c = 0; 72 on_off = complementary; 73 memset(on_off, 0, sizeof(complementary)); 74 75 /* skip GNU extension */ 76 s = (const unsigned char *)applet_opts; 77 if (*s == '+' || *s == '-') 78 s++; 79 while (*s) { 80 if (c >= 32) break; 81 on_off->opt = *s; 82 on_off->switch_on = (1 << c); 83 if (*++s == ':') { 84 on_off->optarg = va_arg(p, void **); 85 while (*++s == ':') /* skip */; 86 } 87 on_off++; 88 c++; 89 } 90 91#if ENABLE_GETOPT_LONG 92 if (applet_long_options) { 93 const char *optstr; 94 unsigned i, count; 95 96 count = 1; 97 optstr = applet_long_options; 98 while (optstr[0]) { 99 optstr += strlen(optstr) + 3; /* skip NUL, has_arg, val */ 100 count++; 101 } 102 /* count == no. of longopts + 1 */ 103 long_options = alloca(count * sizeof(*long_options)); 104 memset(long_options, 0, count * sizeof(*long_options)); 105 i = 0; 106 optstr = applet_long_options; 107 while (--count) { 108 long_options[i].name = optstr; 109 optstr += strlen(optstr) + 1; 110 long_options[i].has_arg = (unsigned char)(*optstr++); 111 /* long_options[i].flag = NULL; */ 112 long_options[i].val = (unsigned char)(*optstr++); 113 i++; 114 } 115 for (l_o = long_options; l_o->name; l_o++) { 116 if (l_o->flag) 117 continue; 118 for (on_off = complementary; on_off->opt != 0; on_off++) 119 if (on_off->opt == l_o->val) 120 goto next_long; 121 if (c >= 32) break; 122 on_off->opt = l_o->val; 123 on_off->switch_on = (1 << c); 124 if (l_o->has_arg != no_argument) 125 on_off->optarg = va_arg(p, void **); 126 c++; 127 next_long: ; 128 } 129 } 130#endif /* ENABLE_GETOPT_LONG */ 131 for (s = (const unsigned char *)opt_complementary; s && *s; s++) { 132 t_complementary *pair; 133 unsigned *pair_switch; 134 135 if (*s == ':') 136 continue; 137 c = s[1]; 138 if (*s == '?') { 139 if (c < '0' || c > '9') { 140 spec_flgs |= SHOW_USAGE_IF_ERROR; 141 } else { 142 max_arg = c - '0'; 143 s++; 144 } 145 continue; 146 } 147 if (*s == '-') { 148 if (c < '0' || c > '9') { 149 if (c == '-') { 150 spec_flgs |= FIRST_ARGV_IS_OPT; 151 s++; 152 } else 153 spec_flgs |= ALL_ARGV_IS_OPTS; 154 } else { 155 min_arg = c - '0'; 156 s++; 157 } 158 continue; 159 } 160 if (*s == '=') { 161 min_arg = max_arg = c - '0'; 162 s++; 163 continue; 164 } 165 for (on_off = complementary; on_off->opt; on_off++) 166 if (on_off->opt == *s) 167 break; 168 if (c == ':' && s[2] == ':') { 169 on_off->list_flg++; 170 continue; 171 } 172 if (c == ':' || c == '\0') { 173 requires |= on_off->switch_on; 174 continue; 175 } 176 if (c == '-' && (s[2] == ':' || s[2] == '\0')) { 177 flags |= on_off->switch_on; 178 on_off->incongruously |= on_off->switch_on; 179 s++; 180 continue; 181 } 182 if (c == *s) { 183 on_off->counter = va_arg(p, int *); 184 s++; 185 } 186 pair = on_off; 187 pair_switch = &(pair->switch_on); 188 for (s++; *s && *s != ':'; s++) { 189 if (*s == '?') { 190 pair_switch = &(pair->requires); 191 } else if (*s == '-') { 192 if (pair_switch == &(pair->switch_off)) 193 pair_switch = &(pair->incongruously); 194 else 195 pair_switch = &(pair->switch_off); 196 } else { 197 for (on_off = complementary; on_off->opt; on_off++) 198 if (on_off->opt == *s) { 199 *pair_switch |= on_off->switch_on; 200 break; 201 } 202 } 203 } 204 s--; 205 } 206 va_end(p); 207 208 if (spec_flgs & FIRST_ARGV_IS_OPT) { 209 if (argv[1] && argv[1][0] != '-' && argv[1][0] != '\0') { 210 argv[1] = xasprintf("-%s", argv[1]); 211 if (ENABLE_FEATURE_CLEAN_UP) 212 spec_flgs |= FREE_FIRST_ARGV_IS_OPT; 213 } 214 } 215 216 /* In case getopt32 was already called, reinit some state */ 217 optind = 1; 218 /* optarg = NULL; opterr = 0; optopt = 0; ?? */ 219 220 /* Note: just "getopt() <= 0" will not work good for 221 * "fake" short options, like this one: 222 * wget $'-\203' "Test: test" http://kernel.org/ 223 * (supposed to act as --header, but doesn't) */ 224#if ENABLE_GETOPT_LONG 225 while ((c = getopt_long(argc, argv, applet_opts, 226 long_options, NULL)) != -1) { 227#else 228 while ((c = getopt(argc, argv, applet_opts)) != -1) { 229#endif 230 c &= 0xff; /* fight libc's sign extends */ 231 loop_arg_is_opt: 232 for (on_off = complementary; on_off->opt != c; on_off++) { 233 /* c==0 if long opt have non NULL flag */ 234 if (on_off->opt == 0 && c != 0) 235 bb_show_usage(); 236 } 237 if (flags & on_off->incongruously) 238 bb_show_usage(); 239 trigger = on_off->switch_on & on_off->switch_off; 240 flags &= ~(on_off->switch_off ^ trigger); 241 flags |= on_off->switch_on ^ trigger; 242 flags ^= trigger; 243 if (on_off->counter) 244 (*(on_off->counter))++; 245 if (on_off->list_flg) { 246 llist_add_to_end((llist_t **)(on_off->optarg), optarg); 247 } else if (on_off->optarg) { 248 *(char **)(on_off->optarg) = optarg; 249 } 250 if (pargv != NULL) 251 break; 252 } 253 254 if (spec_flgs & ALL_ARGV_IS_OPTS) { 255 /* process argv is option, for example "ps" applet */ 256 if (pargv == NULL) 257 pargv = argv + optind; 258 while (*pargv) { 259 c = **pargv; 260 if (c == '\0') { 261 pargv++; 262 } else { 263 (*pargv)++; 264 goto loop_arg_is_opt; 265 } 266 } 267 } 268 269#if (ENABLE_AR || ENABLE_TAR) && ENABLE_FEATURE_CLEAN_UP 270 if (spec_flgs & FREE_FIRST_ARGV_IS_OPT) 271 free(argv[1]); 272#endif 273 /* check depending requires for given options */ 274 for (on_off = complementary; on_off->opt; on_off++) { 275 if (on_off->requires && (flags & on_off->switch_on) && 276 (flags & on_off->requires) == 0) 277 bb_show_usage(); 278 } 279 if (requires && (flags & requires) == 0) 280 bb_show_usage(); 281 argc -= optind; 282 if (argc < min_arg || (max_arg >= 0 && argc > max_arg)) 283 bb_show_usage(); 284 285 option_mask32 = flags; 286 return flags; 287} 288