df275539c4855bcffd34e771ef9926efd0d89f93
[gpxe.git] / src / core / getopt.c
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
19 #include <stdint.h>
20 #include <string.h>
21 #include <vsprintf.h>
22 #include <getopt.h>
23
24 /** @file
25  *
26  * Parse command-line options
27  *
28  */
29
30 /**
31  * Option argument
32  *
33  * This will point to the argument for the most recently returned
34  * option, if applicable.
35  */
36 char *optarg;
37
38 /**
39  * Current option index
40  *
41  * This is an index into the argv[] array.  When getopt() returns -1,
42  * @c optind is the index to the first element that is not an option.
43  */
44 int optind = 1;
45
46 /**
47  * Current option character index
48  *
49  * This is an index into the current element of argv[].
50  */
51 static int nextchar = 0;
52
53 /**
54  * Unrecognised option
55  *
56  * When an unrecognised option is encountered, the actual option
57  * character is stored in @c optopt.
58  */
59 int optopt;
60
61 /**
62  * Reset getopt() internal state
63  *
64  * Due to a limitation of the POSIX getopt() API, it is necessary to
65  * add a call to reset_getopt() before each set of calls to getopt()
66  * or getopt_long().  This arises because POSIX assumes that each
67  * process will parse command line arguments no more than once; this
68  * assumption is not valid within Etherboot.  We work around the
69  * limitation by arranging for execv() to call reset_getopt() before
70  * executing the command.
71  */
72 void reset_getopt ( void ) {
73         optind = 1;
74         nextchar = 0;
75 }
76
77 /**
78  * Get option argument from argv[] array
79  *
80  * @v argc              Argument count
81  * @v argv              Argument list
82  * @ret argument        Option argument, or NULL
83  *
84  * Grab the next element of argv[], if it exists and is not an option.
85  */
86 static const char * get_argv_argument ( int argc, char * const argv[] ) {
87         char *arg;
88
89         /* Don't overrun argv[] */
90         if ( optind >= argc )
91                 return NULL;
92         arg = argv[optind];
93
94         /* If next argv element is an option, then it's not usable as
95          * an argument.
96          */
97         if ( *arg == '-' )
98                 return NULL;
99
100         /** Consume this argv element, and return it */
101         optind++;
102         return arg;
103 }
104
105 /**
106  * Match long option
107  *
108  * @v argc              Argument count
109  * @v argv              Argument list
110  * @v opttext           Option text within current argv[] element
111  * @v longopt           Long option specification
112  * @ret option          Option to return from getopt()
113  * @ret matched         Found a match for this long option
114  */
115 static int match_long_option ( int argc, char * const argv[],
116                                const char *opttext,
117                                const struct option *longopt, int *option ) {
118         size_t optlen;
119         const char *argument = NULL;
120
121         /* Compare option name */
122         optlen = strlen ( longopt->name );
123         if ( strncmp ( opttext, longopt->name, optlen ) != 0 )
124                 return 0;
125
126         /* Check for inline argument */
127         if ( opttext[optlen] == '=' ) {
128                 argument = &opttext[ optlen + 1 ];
129         } else if ( opttext[optlen] ) {
130                 /* Long option with trailing garbage - no match */
131                 return 0;
132         }
133
134         /* Consume this argv element */
135         optind++;
136
137         /* If we want an argument but don't have one yet, try to grab
138          * the next argv element
139          */
140         if ( ( longopt->has_arg != no_argument ) && ( ! argument ) )
141                 argument = get_argv_argument ( argc, argv );
142
143         /* If we need an argument but don't have one, sulk */
144         if ( ( longopt->has_arg == required_argument ) && ( ! argument ) ) {
145                 printf ( "Option \"%s\" requires an argument\n",
146                          longopt->name );
147                 *option = ':';
148                 return 1;
149         }
150
151         /* If we have an argument where we shouldn't have one, sulk */
152         if ( ( longopt->has_arg == no_argument ) && argument ) {
153                 printf ( "Option \"%s\" takes no argument\n", longopt->name );
154                 *option = ':';
155                 return 1;
156         }
157
158         /* Store values and return success */
159         optarg = ( char * ) argument;
160         if ( longopt->flag ) {
161                 *(longopt->flag) = longopt->val;
162                 *option = 0;
163         } else {
164                 *option = longopt->val;
165         }
166         return 1;
167 }
168
169 /**
170  * Match short option
171  *
172  * @v argc              Argument count
173  * @v argv              Argument list
174  * @v opttext           Option text within current argv[] element
175  * @v shortopt          Option character from option specification
176  * @ret option          Option to return from getopt()
177  * @ret matched         Found a match for this short option
178  */
179 static int match_short_option ( int argc, char * const argv[],
180                                 const char *opttext, int shortopt,
181                                 enum getopt_argument_requirement has_arg,
182                                 int *option ) {
183         const char *argument = NULL;
184
185         /* Compare option character */
186         if ( *opttext != shortopt )
187                 return 0;
188
189         /* Consume option character */
190         opttext++;
191         nextchar++;
192         if ( *opttext ) {
193                 if ( has_arg != no_argument ) {
194                         /* Consume remainder of element as inline argument */
195                         argument = opttext;
196                         optind++;
197                         nextchar = 0;
198                 }
199         } else {
200                 /* Reached end of argv element */
201                 optind++;
202                 nextchar = 0;
203         }
204
205         /* If we want an argument but don't have one yet, try to grab
206          * the next argv element
207          */
208         if ( ( has_arg != no_argument ) && ( ! argument ) )
209                 argument = get_argv_argument ( argc, argv );
210
211         /* If we need an argument but don't have one, sulk */
212         if ( ( has_arg == required_argument ) && ( ! argument ) ) {
213                 printf ( "Option \"%c\" requires an argument\n", shortopt );
214                 *option = ':';
215                 return 1;
216         }
217
218         /* Store values and return success */
219         optarg = ( char * ) argument;
220         *option = shortopt;
221         return 1;
222 }
223
224 /**
225  * Parse command-line options
226  *
227  * @v argc              Argument count
228  * @v argv              Argument list
229  * @v optstring         Option specification string
230  * @v longopts          Long option specification table
231  * @ret longindex       Index of long option (or NULL)
232  * @ret option          Option found, or -1 for no more options
233  *
234  */
235 int getopt_long ( int argc, char * const argv[], const char *optstring,
236                   const struct option *longopts, int *longindex ) {
237         const char *opttext = argv[optind];
238         const struct option *longopt;
239         int shortopt;
240         enum getopt_argument_requirement has_arg;
241         int option;
242
243         /* Check for end of options */
244         if ( *(opttext++) != '-' )
245                 return -1;
246
247         /* Check for long options */
248         if ( *(opttext++) == '-' ) {
249                 for ( longopt = longopts ; longopt->name ; longopt++ ) {
250                         if ( ! match_long_option ( argc, argv, opttext,
251                                                    longopt, &option ) )
252                                 continue;
253                         if ( longindex )
254                                 *longindex = ( longopt - longopts );
255                         return option;
256                 }
257                 optopt = '?';
258                 printf ( "Unrecognised option \"--%s\"\n", opttext );
259                 return '?';
260         }
261
262         /* Check for short options */
263         if ( nextchar < 1 )
264                 nextchar = 1;
265         opttext = ( argv[optind] + nextchar );
266         while ( ( shortopt = *(optstring++) ) ) {
267                 has_arg = no_argument;
268                 while ( *optstring == ':' ) {
269                         has_arg++;
270                         optstring++;
271                 }
272                 if ( match_short_option ( argc, argv, opttext, shortopt,
273                                           has_arg, &option ) ) {
274                         return option;
275                 }
276         }
277         optopt = *opttext;
278         printf ( "Unrecognised option \"-%c\"\n", optopt );
279         return '?';
280 }