1/*
2 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19FILE_LICENCE ( GPL2_OR_LATER );
20
21#include <stdint.h>
22#include <string.h>
23#include <stdio.h>
24#include <getopt.h>
25
26/** @file
27 *
28 * Parse command-line options
29 *
30 */
31
32/**
33 * Option argument
34 *
35 * This will point to the argument for the most recently returned
36 * option, if applicable.
37 */
38char *optarg;
39
40/**
41 * Current option index
42 *
43 * This is an index into the argv[] array.  When getopt() returns -1,
44 * @c optind is the index to the first element that is not an option.
45 */
46int optind;
47
48/**
49 * Current option character index
50 *
51 * This is an index into the current element of argv[].
52 */
53int nextchar;
54
55/**
56 * Unrecognised option
57 *
58 * When an unrecognised option is encountered, the actual option
59 * character is stored in @c optopt.
60 */
61int optopt;
62
63/**
64 * Get option argument from argv[] array
65 *
66 * @v argc		Argument count
67 * @v argv		Argument list
68 * @ret argument	Option argument, or NULL
69 *
70 * Grab the next element of argv[], if it exists and is not an option.
71 */
72static const char * get_argv_argument ( int argc, char * const argv[] ) {
73	char *arg;
74
75	/* Don't overrun argv[] */
76	if ( optind >= argc )
77		return NULL;
78	arg = argv[optind];
79
80	/* If next argv element is an option, then it's not usable as
81	 * an argument.
82	 */
83	if ( *arg == '-' )
84		return NULL;
85
86	/** Consume this argv element, and return it */
87	optind++;
88	return arg;
89}
90
91/**
92 * Match long option
93 *
94 * @v argc		Argument count
95 * @v argv		Argument list
96 * @v opttext		Option text within current argv[] element
97 * @v longopt		Long option specification
98 * @ret option		Option to return from getopt()
99 * @ret matched		Found a match for this long option
100 */
101static int match_long_option ( int argc, char * const argv[],
102			       const char *opttext,
103			       const struct option *longopt, int *option ) {
104	size_t optlen;
105	const char *argument = NULL;
106
107	/* Compare option name */
108	optlen = strlen ( longopt->name );
109	if ( strncmp ( opttext, longopt->name, optlen ) != 0 )
110		return 0;
111
112	/* Check for inline argument */
113	if ( opttext[optlen] == '=' ) {
114		argument = &opttext[ optlen + 1 ];
115	} else if ( opttext[optlen] ) {
116		/* Long option with trailing garbage - no match */
117		return 0;
118	}
119
120	/* Consume this argv element */
121	optind++;
122
123	/* If we want an argument but don't have one yet, try to grab
124	 * the next argv element
125	 */
126	if ( ( longopt->has_arg != no_argument ) && ( ! argument ) )
127		argument = get_argv_argument ( argc, argv );
128
129	/* If we need an argument but don't have one, sulk */
130	if ( ( longopt->has_arg == required_argument ) && ( ! argument ) ) {
131		printf ( "Option \"%s\" requires an argument\n",
132			 longopt->name );
133		*option = ':';
134		return 1;
135	}
136
137	/* If we have an argument where we shouldn't have one, sulk */
138	if ( ( longopt->has_arg == no_argument ) && argument ) {
139		printf ( "Option \"%s\" takes no argument\n", longopt->name );
140		*option = ':';
141		return 1;
142	}
143
144	/* Store values and return success */
145	optarg = ( char * ) argument;
146	if ( longopt->flag ) {
147		*(longopt->flag) = longopt->val;
148		*option = 0;
149	} else {
150		*option = longopt->val;
151	}
152	return 1;
153}
154
155/**
156 * Match short option
157 *
158 * @v argc		Argument count
159 * @v argv		Argument list
160 * @v opttext		Option text within current argv[] element
161 * @v shortopt		Option character from option specification
162 * @ret option		Option to return from getopt()
163 * @ret matched		Found a match for this short option
164 */
165static int match_short_option ( int argc, char * const argv[],
166				const char *opttext, int shortopt,
167				enum getopt_argument_requirement has_arg,
168				int *option ) {
169	const char *argument = NULL;
170
171	/* Compare option character */
172	if ( *opttext != shortopt )
173		return 0;
174
175	/* Consume option character */
176	opttext++;
177	nextchar++;
178	if ( *opttext ) {
179		if ( has_arg != no_argument ) {
180			/* Consume remainder of element as inline argument */
181			argument = opttext;
182			optind++;
183			nextchar = 0;
184		}
185	} else {
186		/* Reached end of argv element */
187		optind++;
188		nextchar = 0;
189	}
190
191	/* If we want an argument but don't have one yet, try to grab
192	 * the next argv element
193	 */
194	if ( ( has_arg != no_argument ) && ( ! argument ) )
195		argument = get_argv_argument ( argc, argv );
196
197	/* If we need an argument but don't have one, sulk */
198	if ( ( has_arg == required_argument ) && ( ! argument ) ) {
199		printf ( "Option \"%c\" requires an argument\n", shortopt );
200		*option = ':';
201		return 1;
202	}
203
204	/* Store values and return success */
205	optarg = ( char * ) argument;
206	*option = shortopt;
207	return 1;
208}
209
210/**
211 * Parse command-line options
212 *
213 * @v argc		Argument count
214 * @v argv		Argument list
215 * @v optstring		Option specification string
216 * @v longopts		Long option specification table
217 * @ret longindex	Index of long option (or NULL)
218 * @ret option		Option found, or -1 for no more options
219 *
220 * Note that the caller must arrange for reset_getopt() to be called
221 * before each set of calls to getopt_long().  In Etherboot, this is
222 * done automatically by execv().
223 */
224int getopt_long ( int argc, char * const argv[], const char *optstring,
225		  const struct option *longopts, int *longindex ) {
226	const char *opttext = argv[optind];
227	const struct option *longopt;
228	int shortopt;
229	enum getopt_argument_requirement has_arg;
230	int option;
231
232	/* Check for end of argv array */
233	if ( optind >= argc )
234		return -1;
235
236	/* Check for end of options */
237	if ( *(opttext++) != '-' )
238		return -1;
239
240	/* Check for long options */
241	if ( *(opttext++) == '-' ) {
242		for ( longopt = longopts ; longopt->name ; longopt++ ) {
243			if ( ! match_long_option ( argc, argv, opttext,
244						   longopt, &option ) )
245				continue;
246			if ( longindex )
247				*longindex = ( longopt - longopts );
248			return option;
249		}
250		optopt = '?';
251		printf ( "Unrecognised option \"--%s\"\n", opttext );
252		return '?';
253	}
254
255	/* Check for short options */
256	if ( nextchar < 1 )
257		nextchar = 1;
258	opttext = ( argv[optind] + nextchar );
259	while ( ( shortopt = *(optstring++) ) ) {
260		has_arg = no_argument;
261		while ( *optstring == ':' ) {
262			has_arg++;
263			optstring++;
264		}
265		if ( match_short_option ( argc, argv, opttext, shortopt,
266					  has_arg, &option ) ) {
267			return option;
268		}
269	}
270	optopt = *opttext;
271	printf ( "Unrecognised option \"-%c\"\n", optopt );
272	return '?';
273}
274