VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/getopt.cpp@ 96407

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.9 KB
Line 
1/* $Id: getopt.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * IPRT - Command Line Parsing
4 */
5
6/*
7 * Copyright (C) 2007-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/cidr.h>
42#include <iprt/net.h> /* must come before getopt.h */
43#include <iprt/getopt.h>
44#include "internal/iprt.h"
45
46#include <iprt/assert.h>
47#include <iprt/ctype.h>
48#include <iprt/err.h>
49#include <iprt/message.h>
50#include <iprt/string.h>
51#include <iprt/uuid.h>
52
53
54/*********************************************************************************************************************************
55* Defined Constants And Macros *
56*********************************************************************************************************************************/
57#ifdef IN_RT_STATIC /* We don't need full unicode case insensitive if we ASSUME basic latin only. */
58# define RTStrICmp RTStrICmpAscii
59# define RTStrNICmp RTStrNICmpAscii
60#endif
61
62
63/*********************************************************************************************************************************
64* Global Variables *
65*********************************************************************************************************************************/
66/**
67 * Standard options that gets included unless RTGETOPTINIT_FLAGS_NO_STD_OPTS is
68 * set.
69 */
70static RTGETOPTDEF const g_aStdOptions[] =
71{
72 { "--help", 'h', RTGETOPT_REQ_NOTHING },
73 { "-help", 'h', RTGETOPT_REQ_NOTHING },
74 { "--version", 'V', RTGETOPT_REQ_NOTHING },
75 { "-version", 'V', RTGETOPT_REQ_NOTHING },
76};
77/** The index of --help in g_aStdOptions. Used for some trickery. */
78#define RTGETOPT_STD_OPTIONS_HELP_IDX 0
79
80
81
82RTDECL(int) RTGetOptInit(PRTGETOPTSTATE pState, int argc, char **argv,
83 PCRTGETOPTDEF paOptions, size_t cOptions,
84 int iFirst, uint32_t fFlags)
85{
86 AssertReturn(!(fFlags & ~(RTGETOPTINIT_FLAGS_OPTS_FIRST | RTGETOPTINIT_FLAGS_NO_STD_OPTS)), VERR_INVALID_PARAMETER);
87
88 pState->argv = argv;
89 pState->argc = argc;
90 pState->paOptions = paOptions;
91 pState->cOptions = cOptions;
92 pState->iNext = iFirst;
93 pState->pszNextShort = NULL;
94 pState->pDef = NULL;
95 pState->uIndex = UINT32_MAX;
96 pState->fFlags = fFlags;
97 pState->cNonOptions = 0;
98
99#ifdef RT_STRICT
100 /* validate the options. */
101 for (size_t i = 0; i < cOptions; i++)
102 {
103 Assert(!(paOptions[i].fFlags & ~RTGETOPT_VALID_MASK));
104 Assert(paOptions[i].iShort > 0);
105 Assert(paOptions[i].iShort != VINF_GETOPT_NOT_OPTION);
106 Assert(paOptions[i].iShort != '-');
107 if (paOptions[i].fFlags & RTGETOPT_FLAG_ICASE)
108 {
109 const char *psz = paOptions[i].pszLong;
110 unsigned char ch;
111 while ((ch = *psz++) != '\0')
112 Assert(ch <= 0x7f); /* ASSUMPTION that we can use RTStrICmpAscii and RTStrNICmpAscii. */
113 }
114 }
115#endif
116
117 return VINF_SUCCESS;
118}
119RT_EXPORT_SYMBOL(RTGetOptInit);
120
121#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
122
123/**
124 * Converts an stringified IPv4 address into the RTNETADDRIPV4 representation.
125 *
126 * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
127 * failure.
128 *
129 * @param pszValue The value to convert.
130 * @param pAddr Where to store the result.
131 */
132static int rtgetoptConvertIPv4Addr(const char *pszValue, PRTNETADDRIPV4 pAddr)
133{
134 if (RT_FAILURE(RTNetStrToIPv4Addr(pszValue, pAddr)))
135 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
136 return VINF_SUCCESS;
137}
138
139
140/**
141 * Converts an stringified Ethernet MAC address into the RTMAC representation.
142 *
143 * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
144 * failure.
145 *
146 * @param pszValue The value to convert.
147 * @param pAddr Where to store the result.
148 */
149static int rtgetoptConvertMacAddr(const char *pszValue, PRTMAC pAddr)
150{
151
152 int rc = RTNetStrToMacAddr(pszValue, pAddr);
153 if (RT_FAILURE(rc))
154 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
155
156 return VINF_SUCCESS;
157}
158
159#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
160
161/**
162 * Searches for a long option.
163 *
164 * @returns Pointer to a matching option.
165 * @param pszOption The alleged long option.
166 * @param paOptions Option array.
167 * @param cOptions Number of items in the array.
168 * @param fFlags Init flags.
169 */
170static PCRTGETOPTDEF rtGetOptSearchLong(const char *pszOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
171{
172 PCRTGETOPTDEF pOpt = paOptions;
173 while (cOptions-- > 0)
174 {
175 if (pOpt->pszLong)
176 {
177 if ((pOpt->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
178 {
179 /*
180 * A value is required with the argument. We're trying to be
181 * understanding here and will permit any of the following:
182 * --long12:value, --long12=value, --long12 value,
183 * --long:value, --long=value, --long value,
184 *
185 * If the option is index, then all trailing chars must be
186 * digits. For error reporting reasons we also match where
187 * there is no index.
188 */
189 size_t cchLong = strlen(pOpt->pszLong);
190 if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
191 || ( pOpt->fFlags & RTGETOPT_FLAG_ICASE
192 && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
193 {
194 if (pOpt->fFlags & RTGETOPT_FLAG_INDEX)
195 while (RT_C_IS_DIGIT(pszOption[cchLong]))
196 cchLong++;
197 if ( pszOption[cchLong] == '\0'
198 || pszOption[cchLong] == ':'
199 || pszOption[cchLong] == '=')
200 return pOpt;
201 }
202 }
203 else if (pOpt->fFlags & RTGETOPT_FLAG_INDEX)
204 {
205 /*
206 * The option takes an index but no value.
207 * As above, we also match where there is no index.
208 */
209 size_t cchLong = strlen(pOpt->pszLong);
210 if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
211 || ( pOpt->fFlags & RTGETOPT_FLAG_ICASE
212 && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
213 {
214 while (RT_C_IS_DIGIT(pszOption[cchLong]))
215 cchLong++;
216 if (pszOption[cchLong] == '\0')
217 return pOpt;
218 }
219 }
220 else if ( !strcmp(pszOption, pOpt->pszLong)
221 || ( pOpt->fFlags & RTGETOPT_FLAG_ICASE
222 && !RTStrICmp(pszOption, pOpt->pszLong)))
223 return pOpt;
224 }
225 pOpt++;
226 }
227
228 if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
229 for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
230 if ( !strcmp(pszOption, g_aStdOptions[i].pszLong)
231 || ( g_aStdOptions[i].fFlags & RTGETOPT_FLAG_ICASE
232 && !RTStrICmp(pszOption, g_aStdOptions[i].pszLong)))
233 return &g_aStdOptions[i];
234
235 return NULL;
236}
237
238
239/**
240 * Searches for a matching short option.
241 *
242 * @returns Pointer to a matching option.
243 * @param chOption The option char.
244 * @param paOptions Option array.
245 * @param cOptions Number of items in the array.
246 * @param fFlags Init flags.
247 */
248static PCRTGETOPTDEF rtGetOptSearchShort(int chOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
249{
250 PCRTGETOPTDEF pOpt = paOptions;
251 while (cOptions-- > 0)
252 {
253 if (pOpt->iShort == chOption)
254 return pOpt;
255 pOpt++;
256 }
257
258 if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
259 {
260 for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
261 if (g_aStdOptions[i].iShort == chOption)
262 return &g_aStdOptions[i];
263 if (chOption == '?')
264 return &g_aStdOptions[RTGETOPT_STD_OPTIONS_HELP_IDX];
265 }
266 return NULL;
267}
268
269
270/**
271 * Value string -> Value union.
272 *
273 * @returns IPRT status code.
274 * @param fFlags The value flags.
275 * @param pszValue The value string.
276 * @param pValueUnion Where to return the processed value.
277 */
278static int rtGetOptProcessValue(uint32_t fFlags, const char *pszValue, PRTGETOPTUNION pValueUnion)
279{
280 /*
281 * Transform into a option value as requested.
282 * If decimal conversion fails, we'll check for "0x<xdigit>" and
283 * try a 16 based conversion. We will not interpret any of the
284 * generic ints as octals.
285 */
286 uint32_t const fSwitchValue = fFlags & ( RTGETOPT_REQ_MASK
287 | RTGETOPT_FLAG_HEX
288 | RTGETOPT_FLAG_DEC
289 | RTGETOPT_FLAG_OCT);
290 switch (fSwitchValue)
291 {
292 case RTGETOPT_REQ_STRING:
293 pValueUnion->psz = pszValue;
294 break;
295
296 case RTGETOPT_REQ_BOOL:
297 if ( !RTStrICmp(pszValue, "true")
298 || !RTStrICmp(pszValue, "t")
299 || !RTStrICmp(pszValue, "yes")
300 || !RTStrICmp(pszValue, "y")
301 || !RTStrICmp(pszValue, "enabled")
302 || !RTStrICmp(pszValue, "enable")
303 || !RTStrICmp(pszValue, "en")
304 || !RTStrICmp(pszValue, "e")
305 || !RTStrICmp(pszValue, "on")
306 || !RTStrCmp(pszValue, "1")
307 )
308 pValueUnion->f = true;
309 else if ( !RTStrICmp(pszValue, "false")
310 || !RTStrICmp(pszValue, "f")
311 || !RTStrICmp(pszValue, "no")
312 || !RTStrICmp(pszValue, "n")
313 || !RTStrICmp(pszValue, "disabled")
314 || !RTStrICmp(pszValue, "disable")
315 || !RTStrICmp(pszValue, "dis")
316 || !RTStrICmp(pszValue, "d")
317 || !RTStrICmp(pszValue, "off")
318 || !RTStrCmp(pszValue, "0")
319 )
320 pValueUnion->f = false;
321 else
322 {
323 pValueUnion->psz = pszValue;
324 return VERR_GETOPT_UNKNOWN_OPTION;
325 }
326 break;
327
328 case RTGETOPT_REQ_BOOL_ONOFF:
329 if (!RTStrICmp(pszValue, "on"))
330 pValueUnion->f = true;
331 else if (!RTStrICmp(pszValue, "off"))
332 pValueUnion->f = false;
333 else
334 {
335 pValueUnion->psz = pszValue;
336 return VERR_GETOPT_UNKNOWN_OPTION;
337 }
338 break;
339
340#define MY_INT_CASE(req, type, memb, convfn) \
341 case req: \
342 { \
343 type Value; \
344 if ( convfn(pszValue, 10, &Value) != VINF_SUCCESS \
345 && ( pszValue[0] != '0' \
346 || (pszValue[1] != 'x' && pszValue[1] != 'X') \
347 || !RT_C_IS_XDIGIT(pszValue[2]) \
348 || convfn(pszValue, 16, &Value) != VINF_SUCCESS ) ) \
349 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
350 pValueUnion->memb = Value; \
351 break; \
352 }
353#define MY_BASE_INT_CASE(req, type, memb, convfn, base) \
354 case req: \
355 { \
356 type Value; \
357 if (convfn(pszValue, base, &Value) != VINF_SUCCESS) \
358 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
359 pValueUnion->memb = Value; \
360 break; \
361 }
362
363 MY_INT_CASE(RTGETOPT_REQ_INT8, int8_t, i8, RTStrToInt8Full)
364 MY_INT_CASE(RTGETOPT_REQ_INT16, int16_t, i16, RTStrToInt16Full)
365 MY_INT_CASE(RTGETOPT_REQ_INT32, int32_t, i32, RTStrToInt32Full)
366 MY_INT_CASE(RTGETOPT_REQ_INT64, int64_t, i64, RTStrToInt64Full)
367 MY_INT_CASE(RTGETOPT_REQ_UINT8, uint8_t, u8, RTStrToUInt8Full)
368 MY_INT_CASE(RTGETOPT_REQ_UINT16, uint16_t, u16, RTStrToUInt16Full)
369 MY_INT_CASE(RTGETOPT_REQ_UINT32, uint32_t, u32, RTStrToUInt32Full)
370 MY_INT_CASE(RTGETOPT_REQ_UINT64, uint64_t, u64, RTStrToUInt64Full)
371
372 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_HEX, int8_t, i8, RTStrToInt8Full, 16)
373 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_HEX, int16_t, i16, RTStrToInt16Full, 16)
374 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_HEX, int32_t, i32, RTStrToInt32Full, 16)
375 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_HEX, int64_t, i64, RTStrToInt64Full, 16)
376 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_HEX, uint8_t, u8, RTStrToUInt8Full, 16)
377 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_HEX, uint16_t, u16, RTStrToUInt16Full, 16)
378 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX, uint32_t, u32, RTStrToUInt32Full, 16)
379 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX, uint64_t, u64, RTStrToUInt64Full, 16)
380
381 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_DEC, int8_t, i8, RTStrToInt8Full, 10)
382 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_DEC, int16_t, i16, RTStrToInt16Full, 10)
383 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_DEC, int32_t, i32, RTStrToInt32Full, 10)
384 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_DEC, int64_t, i64, RTStrToInt64Full, 10)
385 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_DEC, uint8_t, u8, RTStrToUInt8Full, 10)
386 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_DEC, uint16_t, u16, RTStrToUInt16Full, 10)
387 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_DEC, uint32_t, u32, RTStrToUInt32Full, 10)
388 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_DEC, uint64_t, u64, RTStrToUInt64Full, 10)
389
390 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_OCT, int8_t, i8, RTStrToInt8Full, 8)
391 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_OCT, int16_t, i16, RTStrToInt16Full, 8)
392 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_OCT, int32_t, i32, RTStrToInt32Full, 8)
393 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_OCT, int64_t, i64, RTStrToInt64Full, 8)
394 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_OCT, uint8_t, u8, RTStrToUInt8Full, 8)
395 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_OCT, uint16_t, u16, RTStrToUInt16Full, 8)
396 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT, uint32_t, u32, RTStrToUInt32Full, 8)
397 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_OCT, uint64_t, u64, RTStrToUInt64Full, 8)
398
399#undef MY_INT_CASE
400#undef MY_BASE_INT_CASE
401
402#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
403
404 case RTGETOPT_REQ_IPV4ADDR:
405 {
406 RTNETADDRIPV4 Addr;
407 if (rtgetoptConvertIPv4Addr(pszValue, &Addr) != VINF_SUCCESS)
408 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
409 pValueUnion->IPv4Addr = Addr;
410 break;
411 }
412
413 case RTGETOPT_REQ_IPV4CIDR:
414 {
415 RTNETADDRIPV4 network;
416 RTNETADDRIPV4 netmask;
417 if (RT_FAILURE(RTCidrStrToIPv4(pszValue, &network, &netmask)))
418 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
419 pValueUnion->CidrIPv4.IPv4Network.u = network.u;
420 pValueUnion->CidrIPv4.IPv4Netmask.u = netmask.u;
421 break;
422 }
423
424 case RTGETOPT_REQ_MACADDR:
425 {
426 RTMAC Addr;
427 if (rtgetoptConvertMacAddr(pszValue, &Addr) != VINF_SUCCESS)
428 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
429 pValueUnion->MacAddr = Addr;
430 break;
431 }
432
433#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
434
435 case RTGETOPT_REQ_UUID:
436 {
437 RTUUID Uuid;
438 if (RTUuidFromStr(&Uuid, pszValue) != VINF_SUCCESS)
439 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
440 pValueUnion->Uuid = Uuid;
441 break;
442 }
443
444#define MY_INT_PAIR_CASE(a_fReqValue, a_fReqValueOptional, a_Type, a_MemberPrefix, a_fnConv, a_ConvBase, a_DefaultValue) \
445 case a_fReqValue: \
446 case a_fReqValueOptional: \
447 { \
448 /* First value: */ \
449 a_Type Value1; \
450 char *pszNext = NULL; \
451 unsigned uBase = pszValue[0] == '0' \
452 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
453 && RT_C_IS_XDIGIT(pszValue[2]) \
454 ? 16 : a_ConvBase; \
455 int rc = a_fnConv(pszValue, &pszNext, uBase, &Value1); \
456 if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES) \
457 { \
458 /* The second value, could be optional: */ \
459 a_Type Value2 = a_DefaultValue; \
460 pszValue = pszNext;\
461 if (pszValue) \
462 { \
463 while (RT_C_IS_BLANK(*pszValue)) \
464 pszValue++; \
465 if (*pszValue == ':' || *pszValue == '/' || *pszValue == '|') \
466 do pszValue++; \
467 while (RT_C_IS_BLANK(*pszValue)); \
468 if (pszValue != pszNext) \
469 { \
470 uBase = pszValue[0] == '0' \
471 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
472 && RT_C_IS_XDIGIT(pszValue[2]) \
473 ? 16 : a_ConvBase; \
474 rc = a_fnConv(pszValue, &pszNext, uBase, &Value2); \
475 if (rc == VINF_SUCCESS) \
476 { /* likely */ } \
477 else \
478 AssertMsgFailedReturn(("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase), \
479 VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
480 } \
481 else if (fSwitchValue != (a_fReqValueOptional)) \
482 AssertMsgFailedReturn(("x\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
483 } \
484 else if (fSwitchValue != (a_fReqValueOptional)) \
485 AssertMsgFailedReturn(("y\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
486 pValueUnion->a_MemberPrefix##Second = Value2; \
487 pValueUnion->a_MemberPrefix##First = Value1; \
488 break; \
489 } \
490 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
491 }
492
493 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR,
494 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
495 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
496 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
497 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
498 uint32_t, PairU32.u, RTStrToUInt32Ex, 16, UINT32_MAX)
499 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
500 uint32_t, PairU32.u, RTStrToUInt32Ex, 8, UINT32_MAX)
501
502 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR,
503 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
504 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
505 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
506 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
507 uint64_t, PairU64.u, RTStrToUInt64Ex, 16, UINT64_MAX)
508 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
509 uint64_t, PairU64.u, RTStrToUInt64Ex, 8, UINT64_MAX)
510
511 default:
512 AssertMsgFailed(("f=%#x\n", fFlags));
513 return VERR_INTERNAL_ERROR;
514 }
515
516 return VINF_SUCCESS;
517}
518
519
520/**
521 * Moves one argv option entries.
522 *
523 * @param papszTo Destination.
524 * @param papszFrom Source.
525 */
526static void rtGetOptMoveArgvEntries(char **papszTo, char **papszFrom)
527{
528 if (papszTo != papszFrom)
529 {
530 Assert((uintptr_t)papszTo < (uintptr_t)papszFrom);
531 char * const pszMoved = papszFrom[0];
532 memmove(&papszTo[1], &papszTo[0], (uintptr_t)papszFrom - (uintptr_t)papszTo);
533 papszTo[0] = pszMoved;
534 }
535}
536
537
538RTDECL(int) RTGetOpt(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion)
539{
540 /*
541 * Reset the variables kept in state.
542 */
543 pState->pDef = NULL;
544 pState->uIndex = UINT32_MAX;
545
546 /*
547 * Make sure the union is completely cleared out, whatever happens below.
548 */
549 pValueUnion->u64 = 0;
550 pValueUnion->pDef = NULL;
551
552 /*
553 * The next option.
554 */
555 bool fShort;
556 int iThis;
557 const char *pszArgThis;
558 PCRTGETOPTDEF pOpt;
559
560 if (pState->pszNextShort)
561 {
562 /*
563 * We've got short options left over from the previous call.
564 */
565 pOpt = rtGetOptSearchShort(*pState->pszNextShort, pState->paOptions, pState->cOptions, pState->fFlags);
566 if (!pOpt)
567 {
568 pValueUnion->psz = pState->pszNextShort;
569 return VERR_GETOPT_UNKNOWN_OPTION;
570 }
571 pState->pszNextShort++;
572 pszArgThis = pState->pszNextShort - 2;
573 iThis = pState->iNext;
574 fShort = true;
575 }
576 else
577 {
578 /*
579 * Pop off the next argument. Sorting options and dealing with the
580 * dash-dash makes this a little extra complicated.
581 */
582 for (;;)
583 {
584 if (pState->iNext >= pState->argc)
585 return 0;
586
587 if (pState->cNonOptions)
588 {
589 if (pState->cNonOptions == INT32_MAX)
590 {
591 pValueUnion->psz = pState->argv[pState->iNext++];
592 return VINF_GETOPT_NOT_OPTION;
593 }
594
595 if (pState->iNext + pState->cNonOptions >= pState->argc)
596 {
597 pState->cNonOptions = INT32_MAX;
598 continue;
599 }
600 }
601
602 iThis = pState->iNext++;
603 pszArgThis = pState->argv[iThis + pState->cNonOptions];
604
605 /*
606 * Do a long option search first and then a short option one.
607 * This way we can make sure single dash long options doesn't
608 * get mixed up with short ones.
609 */
610 pOpt = rtGetOptSearchLong(pszArgThis, pState->paOptions, pState->cOptions, pState->fFlags);
611 if ( !pOpt
612 && pszArgThis[0] == '-'
613 && pszArgThis[1] != '-'
614 && pszArgThis[1] != '\0')
615 {
616 pOpt = rtGetOptSearchShort(pszArgThis[1], pState->paOptions, pState->cOptions, pState->fFlags);
617 fShort = pOpt != NULL;
618 }
619 else
620 fShort = false;
621
622 /* Look for dash-dash. */
623 if (!pOpt && !strcmp(pszArgThis, "--"))
624 {
625 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
626 pState->cNonOptions = INT32_MAX;
627 continue;
628 }
629
630 /* Options first hacks. */
631 if (pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST)
632 {
633 if (pOpt)
634 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
635 else if (*pszArgThis == '-')
636 {
637 pValueUnion->psz = pszArgThis;
638 return VERR_GETOPT_UNKNOWN_OPTION;
639 }
640 else
641 {
642 /* not an option, add it to the non-options and try again. */
643 pState->iNext--;
644 pState->cNonOptions++;
645
646 /* Switch to returning non-options if we've reached the end. */
647 if (pState->iNext + pState->cNonOptions >= pState->argc)
648 pState->cNonOptions = INT32_MAX;
649 continue;
650 }
651 }
652
653 /* done */
654 break;
655 }
656 }
657
658 if (pOpt)
659 {
660 pValueUnion->pDef = pOpt; /* in case of no value or error. */
661
662 if ((pOpt->fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
663 {
664 /*
665 * Find the argument value.
666 *
667 * A value is required with the argument. We're trying to be
668 * understanding here and will permit any of the following:
669 * -svalue, -s value, -s:value and -s=value
670 * (Ditto for long options.)
671 */
672 const char *pszValue;
673 if (fShort)
674 {
675 if (pszArgThis[2] == '\0')
676 {
677 if (iThis + 1 >= pState->argc)
678 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
679 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
680 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
681 pState->iNext++;
682 }
683 else /* same argument. */
684 pszValue = &pszArgThis[2 + (pszArgThis[2] == ':' || pszArgThis[2] == '=')];
685 if (pState->pszNextShort)
686 {
687 pState->pszNextShort = NULL;
688 pState->iNext++;
689 }
690 }
691 else
692 {
693 size_t cchLong = strlen(pOpt->pszLong);
694 if (pOpt->fFlags & RTGETOPT_FLAG_INDEX)
695 {
696
697 if (pszArgThis[cchLong] == '\0')
698 return VERR_GETOPT_INDEX_MISSING;
699
700 uint32_t uIndex;
701 char *pszRet = NULL;
702 int rc = RTStrToUInt32Ex(&pszArgThis[cchLong], &pszRet, 10, &uIndex);
703 if (rc == VWRN_TRAILING_CHARS)
704 {
705 if ( pszRet[0] != ':'
706 && pszRet[0] != '=')
707 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
708 pState->uIndex = uIndex;
709 pszValue = pszRet + 1;
710 }
711 else if (rc == VINF_SUCCESS)
712 {
713 if (iThis + 1 + pState->cNonOptions >= pState->argc)
714 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
715 pState->uIndex = uIndex;
716 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
717 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
718 pState->iNext++;
719 }
720 else
721 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
722 }
723 else
724 {
725 if (pszArgThis[cchLong] == '\0')
726 {
727 if (iThis + 1 + pState->cNonOptions >= pState->argc)
728 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
729 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
730 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
731 pState->iNext++;
732 }
733 else /* same argument. */
734 pszValue = &pszArgThis[cchLong + 1];
735 }
736 }
737
738 /*
739 * Set up the ValueUnion.
740 */
741 int rc = rtGetOptProcessValue(pOpt->fFlags, pszValue, pValueUnion);
742 if (RT_FAILURE(rc))
743 return rc;
744 }
745 else if (fShort)
746 {
747 /*
748 * Deal with "compressed" short option lists, correcting the next
749 * state variables for the start and end cases.
750 */
751 if (pszArgThis[2])
752 {
753 if (!pState->pszNextShort)
754 {
755 /* start */
756 pState->pszNextShort = &pszArgThis[2];
757 pState->iNext--;
758 }
759 }
760 else if (pState->pszNextShort)
761 {
762 /* end */
763 pState->pszNextShort = NULL;
764 pState->iNext++;
765 }
766 }
767 else if (pOpt->fFlags & RTGETOPT_FLAG_INDEX)
768 {
769 size_t cchLong = strlen(pOpt->pszLong);
770 if (pszArgThis[cchLong] == '\0')
771 return VERR_GETOPT_INDEX_MISSING;
772
773 uint32_t uIndex;
774 if (RTStrToUInt32Full(&pszArgThis[cchLong], 10, &uIndex) == VINF_SUCCESS)
775 pState->uIndex = uIndex;
776 else
777 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
778 }
779
780 pState->pDef = pOpt;
781 return pOpt->iShort;
782 }
783
784 /*
785 * Not a known option argument. If it starts with a switch char (-) we'll
786 * fail with unknown option, and if it doesn't we'll return it as a non-option.
787 */
788 if (*pszArgThis == '-')
789 {
790 pValueUnion->psz = pszArgThis;
791 return VERR_GETOPT_UNKNOWN_OPTION;
792 }
793
794 pValueUnion->psz = pszArgThis;
795 return VINF_GETOPT_NOT_OPTION;
796}
797RT_EXPORT_SYMBOL(RTGetOpt);
798
799
800RTDECL(int) RTGetOptFetchValue(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion, uint32_t fFlags)
801{
802 /*
803 * Validate input.
804 */
805 PCRTGETOPTDEF pOpt = pState->pDef;
806 AssertReturn(!(fFlags & ~RTGETOPT_VALID_MASK), VERR_INVALID_PARAMETER);
807 AssertReturn((fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING, VERR_INVALID_PARAMETER);
808
809 /*
810 * Make sure the union is completely cleared out, whatever happens below.
811 */
812 pValueUnion->u64 = 0;
813 pValueUnion->pDef = NULL;
814
815 /*
816 * Pop off the next argument and convert it into a value union.
817 */
818 if (pState->iNext >= pState->argc)
819 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
820 int iThis = pState->iNext++;
821 const char *pszValue = pState->argv[iThis + (pState->cNonOptions != INT32_MAX ? pState->cNonOptions : 0)];
822 pValueUnion->pDef = pOpt; /* in case of no value or error. */
823
824 if (pState->cNonOptions && pState->cNonOptions != INT32_MAX)
825 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
826
827 return rtGetOptProcessValue(fFlags, pszValue, pValueUnion);
828}
829RT_EXPORT_SYMBOL(RTGetOptFetchValue);
830
831
832RTDECL(char **) RTGetOptNonOptionArrayPtr(PRTGETOPTSTATE pState)
833{
834 AssertReturn(pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST, NULL);
835 return &pState->argv[pState->iNext - 1];
836}
837RT_EXPORT_SYMBOL(RTGetOptNonOptionArrayPtr);
838
839
840RTDECL(RTEXITCODE) RTGetOptPrintError(int ch, PCRTGETOPTUNION pValueUnion)
841{
842 if (ch == VINF_GETOPT_NOT_OPTION)
843 RTMsgError("Invalid parameter: %s", pValueUnion->psz);
844 else if (ch > 0)
845 {
846 if (RT_C_IS_GRAPH(ch))
847 RTMsgError("Unhandled option: -%c", ch);
848 else
849 RTMsgError("Unhandled option: %i (%#x)", ch, ch);
850 }
851 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
852 RTMsgError("Unknown option: '%s'", pValueUnion->psz);
853 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
854 /** @todo r=klaus not really ideal, as the value isn't available */
855 RTMsgError("The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
856 else if (pValueUnion->pDef)
857 RTMsgError("%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
858 else
859 RTMsgError("%Rrs\n", ch);
860
861 return RTEXITCODE_SYNTAX;
862}
863RT_EXPORT_SYMBOL(RTGetOptPrintError);
864
865
866RTDECL(ssize_t) RTGetOptFormatError(char *pszBuf, size_t cbBuf, int ch, PCRTGETOPTUNION pValueUnion)
867{
868 ssize_t cchRet;
869 if (ch == VINF_GETOPT_NOT_OPTION)
870 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Invalid parameter: %s", pValueUnion->psz);
871 else if (ch > 0)
872 {
873 if (RT_C_IS_GRAPH(ch))
874 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: -%c", ch);
875 else
876 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: %i (%#x)", ch, ch);
877 }
878 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
879 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unknown option: '%s'", pValueUnion->psz);
880 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
881 /** @todo r=klaus not really ideal, as the value isn't available */
882 cchRet = RTStrPrintf2(pszBuf, cbBuf, "The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
883 else if (pValueUnion->pDef)
884 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
885 else
886 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%Rrs\n", ch);
887
888 return cchRet;
889}
890RT_EXPORT_SYMBOL(RTGetOptFormatError);
891
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette