blob: 542b8cf0c8686933a57f3eabe4cd72e5d6b593ad [file] [log] [blame]
Rob Landley8324b892006-11-19 02:49:22 -05001/* vi: set sw=4 ts=4 :
2 * args.c - Command line argument parsing.
3 *
4 * Copyright 2006 Rob Landley <rob@landley.net>
5 */
6
7#include "toys.h"
8
9// Design goals:
10// Don't use getopt()
11// Don't permute original arguments.
12// handle --long gracefully "(noshort)a(along)b(blong1)(blong2)"
13// After each argument:
14// Note that pointer and long are always the same size, even on 64 bit.
15// : plus a string argument, keep most recent if more than one
16// * plus a string argument, appended to a list
Rob Landley028a5442007-01-25 16:11:23 -050017// # plus a signed long argument (TODO: Bounds checking?)
Rob Landley8324b892006-11-19 02:49:22 -050018// @ plus an occurrence counter (which is a long)
19// | this is required. If more than one marked, only one required.
20// (longopt)
21// +X enabling this enables X (switch on)
22// ~X enabling this disables X (switch off)
23// x~x means toggle x, I.E. specifying it again switches it off.
24// !X die with error if X already set (x!x die if x supplied twice)
25// [yz] needs at least one of y or z.
26// at the beginning:
27// + stop at first nonoption argument
Rob Landley8324b892006-11-19 02:49:22 -050028// <0 at least # leftover arguments needed (default 0)
29// >9 at most # leftover arguments needed (default MAX_INT)
Rob Landley028a5442007-01-25 16:11:23 -050030// ? don't show_usage() on unknown argument.
Rob Landley8324b892006-11-19 02:49:22 -050031// & first argument has imaginary dash (ala tar/ps)
32// If given twice, all arguments have imaginary dash
33
34// Notes from getopt man page
35// - and -- cannot be arguments.
36// -- force end of arguments
37// - is a synonym for stdin in file arguments
38// -abc means -a -b -c
39
40/* This uses a getopt-like option string, but not getopt() itself.
41 *
Rob Landley028a5442007-01-25 16:11:23 -050042 * Each option in options corresponds to a bit position in the return
43 * value (last argument is (1<<0), the next to last is (1<<1) and so on).
44 * If the option isn't seen in argv[] its bit is 0.
45 *
46 * Options which have an argument fill in the corresponding slot in the global
47 * toys.command struct, which it treats as an array of longs (note that
48 * sizeof(long)==sizeof(pointer) is guaranteed by LP64).
49 *
50 * You don't have to free the option strings, which point into the environment
51 * space. List list objects should be freed by main() when command_main()
52 * returns.
Rob Landley8324b892006-11-19 02:49:22 -050053 *
54 * Example:
Rob Landley028a5442007-01-25 16:11:23 -050055 * get_optflags() when toys.which->options="ab:c:d"
Rob Landley8324b892006-11-19 02:49:22 -050056 * argv = ["command", "-b", "fruit", "-d"]
Rob Landley028a5442007-01-25 16:11:23 -050057 * flags = 5, toy[0]=NULL, toy[1]="fruit";
Rob Landley8324b892006-11-19 02:49:22 -050058 */
59
Rob Landley028a5442007-01-25 16:11:23 -050060//
Rob Landley8324b892006-11-19 02:49:22 -050061struct opts {
62 struct opts *next;
63 char c;
64 int type;
65 int shift;
Rob Landley58c6c1b2006-11-25 13:34:51 -050066 long *arg;
Rob Landley8324b892006-11-19 02:49:22 -050067};
68
69struct getoptflagstate
70{
71 int argc;
72 char *arg;
73 struct opts *opts, *this;
74 int noerror, nodash_now;
75};
76
77static struct getoptflagstate gof;
78
Rob Landleyfdb667e2006-11-24 00:15:21 -050079static void gotflag(void)
Rob Landley8324b892006-11-19 02:49:22 -050080{
Rob Landley8324b892006-11-19 02:49:22 -050081 int type;
Rob Landley8324b892006-11-19 02:49:22 -050082
83 // Did we recognize this option?
84 if (!gof.this && !gof.noerror) error_exit("Unknown option %s\n", gof.arg);
85 else toys.optflags |= 1 << gof.this->shift;
86
87 // Does this option take an argument?
88 gof.arg++;
Rob Landley58c6c1b2006-11-25 13:34:51 -050089 type = gof.this->type & 255;
90 if (type) {
91
92 // Handle "-xblah" and "-x blah", but also a third case: "abxc blah"
93 // to make "tar xCjfv blah1 blah2 thingy" work like
Rob Landley8324b892006-11-19 02:49:22 -050094 // "tar -x -C blah1 -j -f blah2 -v thingy"
95 if (!gof.nodash_now && !*gof.arg) {
96 gof.arg = toys.argv[++gof.argc];
97 if (!gof.arg) error_exit("Missing argument");
Rob Landley8324b892006-11-19 02:49:22 -050098 }
Rob Landley8324b892006-11-19 02:49:22 -050099
Rob Landley58c6c1b2006-11-25 13:34:51 -0500100 // Grab argument.
Rob Landleyfdb667e2006-11-24 00:15:21 -0500101 if (!gof.arg && !(gof.arg = toys.argv[++gof.argc]))
102 error_exit("Missing argument");
Rob Landley58c6c1b2006-11-25 13:34:51 -0500103 if (type == ':') *(gof.this->arg) = (long)gof.arg;
Rob Landleyfdb667e2006-11-24 00:15:21 -0500104 else if (type == '*') {
105 struct arg_list *temp, **list;
106 list = (struct arg_list **)gof.this->arg;
107 temp = xmalloc(sizeof(struct arg_list));
108 temp->arg = gof.arg;
109 temp->next = *list;
110 *list = temp;
Rob Landleyf5757162007-02-16 21:08:22 -0500111 } else if (type == '#') *(gof.this->arg) = atolx((char *)gof.arg);
Rob Landley028a5442007-01-25 16:11:23 -0500112 else if (type == '@') {
Rob Landleyfdb667e2006-11-24 00:15:21 -0500113 }
Rob Landley8324b892006-11-19 02:49:22 -0500114
Rob Landleyfdb667e2006-11-24 00:15:21 -0500115 gof.arg = "";
116 }
Rob Landley58c6c1b2006-11-25 13:34:51 -0500117
118 gof.this = NULL;
Rob Landley8324b892006-11-19 02:49:22 -0500119}
120
121// Fill out toys.optflags and toys.optargs. This isn't reentrant because
122// we don't bzero(&gof, sizeof(gof));
123
124void get_optflags(void)
125{
Rob Landley54ebcce2006-11-19 20:35:19 -0500126 int stopearly = 0, optarg = 0, nodash = 0, minargs = 0, maxargs;
Rob Landley8324b892006-11-19 02:49:22 -0500127 struct longopts {
128 struct longopts *next;
129 struct opts *opt;
130 char *str;
131 int len;
132 } *longopts = NULL;
133 long *nextarg = (long *)&toy;
134 char *options = toys.which->options;
135
Rob Landleyd06c58d2007-10-11 15:36:36 -0500136 if (CFG_HELP) toys.exithelp++;
Rob Landley54ebcce2006-11-19 20:35:19 -0500137 // Allocate memory for optargs
138 maxargs = 0;
139 while (toys.argv[maxargs++]);
140 toys.optargs = xzalloc(sizeof(char *)*maxargs);
141 maxargs = INT_MAX;
142
143 // Parse option format
Rob Landley2a813ff2006-11-19 17:29:35 -0500144 if (options) {
145 // Parse leading special behavior indicators
146 for (;;) {
147 if (*options == '+') stopearly++;
148 else if (*options == '<') minargs=*(++options)-'0';
149 else if (*options == '>') maxargs=*(++options)-'0';
Rob Landley028a5442007-01-25 16:11:23 -0500150 else if (*options == '?') gof.noerror++;
Rob Landley2a813ff2006-11-19 17:29:35 -0500151 else if (*options == '&') nodash++;
152 else break;
153 options++;
Rob Landley8324b892006-11-19 02:49:22 -0500154 }
Rob Landley8324b892006-11-19 02:49:22 -0500155
Rob Landley2a813ff2006-11-19 17:29:35 -0500156 // Parse rest of opts into array
157 while (*options) {
Rob Landley8324b892006-11-19 02:49:22 -0500158
Rob Landley2a813ff2006-11-19 17:29:35 -0500159 // Allocate a new option entry when necessary
160 if (!gof.this) {
161 gof.this = xzalloc(sizeof(struct opts));
162 gof.this->next = gof.opts;
163 gof.opts = gof.this;
164 }
165 // Each option must start with (or an option character. (Bare
166 // longopts only come at the start of the string.)
167 if (*options == '(') {
168 char *end;
169 struct longopts *lo = xmalloc(sizeof(struct longopts));
Rob Landley8324b892006-11-19 02:49:22 -0500170
Rob Landley2a813ff2006-11-19 17:29:35 -0500171 // Find the end of the longopt
172 for (end = ++options; *end && *end != ')'; end++);
Rob Landleyde05a702007-01-31 14:37:01 -0500173 if (CFG_TOYBOX_DEBUG && !*end)
174 error_exit("Unterminated optstring");
Rob Landley8324b892006-11-19 02:49:22 -0500175
Rob Landley2a813ff2006-11-19 17:29:35 -0500176 // Allocate and init a new struct longopts
177 lo = xmalloc(sizeof(struct longopts));
178 lo->next = longopts;
179 lo->opt = gof.this;
180 lo->str = options;
181 lo->len = end-options;
182 longopts = lo;
183 options = end;
Rob Landley8324b892006-11-19 02:49:22 -0500184
Rob Landley2a813ff2006-11-19 17:29:35 -0500185 // For leading longopts (with no corresponding short opt), note
186 // that this option struct has been used.
187 gof.this->shift++;
Rob Landley8324b892006-11-19 02:49:22 -0500188
Rob Landley2a813ff2006-11-19 17:29:35 -0500189 // If this is the start of a new option that wasn't a longopt,
Rob Landley8324b892006-11-19 02:49:22 -0500190
Rob Landley028a5442007-01-25 16:11:23 -0500191 } else if (index(":*#@", *options)) {
Rob Landley2a813ff2006-11-19 17:29:35 -0500192 gof.this->type |= *options;
Rob Landley2a813ff2006-11-19 17:29:35 -0500193 } else if (*options == '|') {
194 } else if (*options == '+') {
195 } else if (*options == '~') {
196 } else if (*options == '!') {
197 } else if (*options == '[') {
Rob Landley8324b892006-11-19 02:49:22 -0500198
Rob Landley2a813ff2006-11-19 17:29:35 -0500199 // At this point, we've hit the end of the previous option. The
200 // current character is the start of a new option. If we've already
201 // assigned an option to this struct, loop to allocate a new one.
202 // (It'll get back here afterwards.)
203 } else if(gof.this->shift || gof.this->c) {
204 gof.this = NULL;
205 continue;
206
207 // Claim this option, loop to see what's after it.
208 } else gof.this->c = *options;
209
210 options++;
211 }
Rob Landley8324b892006-11-19 02:49:22 -0500212 }
213
Rob Landley028a5442007-01-25 16:11:23 -0500214 // Initialize shift bits and pointers to store arguments. (We have to
215 // calculate this ahead of time because longopts jump into the middle of
216 // the list.)
Rob Landley8324b892006-11-19 02:49:22 -0500217 gof.argc = 0;
Rob Landley028a5442007-01-25 16:11:23 -0500218 for (gof.this = gof.opts; gof.this; gof.this = gof.this->next) {
Rob Landley8324b892006-11-19 02:49:22 -0500219 gof.this->shift = gof.argc++;
Rob Landley028a5442007-01-25 16:11:23 -0500220 if (gof.this->type & 255) {
221 gof.this->arg = (void *)nextarg;
222 *(nextarg++) = 0;
223 }
224 }
Rob Landley8324b892006-11-19 02:49:22 -0500225
226 // Iterate through command line arguments, skipping argv[0]
227 for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) {
Rob Landleyfdb667e2006-11-24 00:15:21 -0500228 gof.arg = toys.argv[gof.argc];
229 gof.this = NULL;
Rob Landley8324b892006-11-19 02:49:22 -0500230
231 // Parse this argument
232 if (stopearly>1) goto notflag;
233
234 gof.nodash_now = 0;
235
236 // Various things with dashes
Rob Landleyfdb667e2006-11-24 00:15:21 -0500237 if (*gof.arg == '-') {
Rob Landley8324b892006-11-19 02:49:22 -0500238
239 // Handle -
Rob Landleyfdb667e2006-11-24 00:15:21 -0500240 if (!gof.arg[1]) goto notflag;
241 gof.arg++;
242 if (*gof.arg=='-') {
Rob Landley8324b892006-11-19 02:49:22 -0500243 struct longopts *lo;
244
Rob Landleyfdb667e2006-11-24 00:15:21 -0500245 gof.arg++;
Rob Landley8324b892006-11-19 02:49:22 -0500246 // Handle --
Rob Landleyfdb667e2006-11-24 00:15:21 -0500247 if (!*gof.arg) {
Rob Landley8324b892006-11-19 02:49:22 -0500248 stopearly += 2;
249 goto notflag;
250 }
251 // Handle --longopt
252
253 for (lo = longopts; lo; lo = lo->next) {
Rob Landleyfdb667e2006-11-24 00:15:21 -0500254 if (!strncmp(gof.arg, lo->str, lo->len)) {
255 if (gof.arg[lo->len]) {
256 if (gof.arg[lo->len]=='='
Rob Landley8324b892006-11-19 02:49:22 -0500257 && (lo->opt->type & 255))
258 {
Rob Landleyfdb667e2006-11-24 00:15:21 -0500259 gof.arg += lo->len;
Rob Landley8324b892006-11-19 02:49:22 -0500260 } else continue;
Rob Landleyfdb667e2006-11-24 00:15:21 -0500261 }
262 // It's a match.
263 gof.arg = "";
Rob Landley8324b892006-11-19 02:49:22 -0500264 gof.this = lo->opt;
265 break;
266 }
267 }
Rob Landleyfdb667e2006-11-24 00:15:21 -0500268
269 // Long option parsed, handle option.
Rob Landley8324b892006-11-19 02:49:22 -0500270 gotflag();
271 continue;
272 }
273
274 // Handle things that don't start with a dash.
275 } else {
276 if (nodash && (nodash>1 || gof.argc == 1)) gof.nodash_now = 1;
277 else goto notflag;
278 }
279
280 // At this point, we have the args part of -args. Loop through
281 // each entry (could be -abc meaning -a -b -c)
Rob Landleyfdb667e2006-11-24 00:15:21 -0500282 while (*gof.arg) {
283
Rob Landley8324b892006-11-19 02:49:22 -0500284 // Identify next option char.
Rob Landleyfdb667e2006-11-24 00:15:21 -0500285 for (gof.this = gof.opts; gof.this; gof.this = gof.this->next)
286 if (*gof.arg == gof.this->c) break;
287
288 // Handle option char (advancing past what was used)
289 gotflag();
Rob Landley8324b892006-11-19 02:49:22 -0500290 }
291 continue;
292
293 // Not a flag, save value in toys.optargs[]
294notflag:
295 if (stopearly) stopearly++;
296 toys.optargs[optarg++] = toys.argv[gof.argc];
297 }
298
299 // Sanity check
Rob Landley7aa9d8f2007-06-18 00:12:43 -0400300 if (optarg<minargs)
301 error_exit("Need %d argument%s", minargs, minargs ? "s" : "");
302 if (optarg>maxargs)
303 error_exit("Max %d argument%s", maxargs, maxargs ? "s" : "");
Rob Landleyd06c58d2007-10-11 15:36:36 -0500304 if (CFG_HELP) toys.exithelp = 0;
Rob Landley8324b892006-11-19 02:49:22 -0500305}
Rob Landley1322beb2007-01-07 22:51:12 -0500306
307// Loop through files listed on the command line
308
309static int dofileargs(char ***files, int fd, int iswrite)
310{
311 char *filename = *((*files)++);
312 static int flags[] = {O_RDONLY, O_CREAT|O_TRUNC, O_RDWR};
313
314 if (fd != -1) close(fd);
315
316 for (;;) {
317
318 // Are there no more files?
319 if (!*filename)
320 return (fd == -1) ? iswrite : -1;
321
322 // A filename of "-" means stdin.
323 if (*filename == '-' && !filename[1]) return 0;
324
325 fd = xcreate(filename, flags[iswrite], 0777);
326 }
327}
328
329int readfileargs(char ***files, int fd)
330{
331 return dofileargs(files, fd, 0);
332}
333
334int writefileargs(char ***files, int fd)
335{
336 return dofileargs(files, fd, 1);
337}