VirtualBox

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

Last change on this file was 99249, checked in by vboxsync, 14 months ago

IPRT/RTGetOpt: Simplification. bugref:10302

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.9 KB
Line 
1/* $Id: getopt.cpp 99249 2023-03-31 09:03:56Z vboxsync $ */
2/** @file
3 * IPRT - Command Line Parsing
4 */
5
6/*
7 * Copyright (C) 2007-2023 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/uni.h>
52#include <iprt/uuid.h>
53
54
55/*********************************************************************************************************************************
56* Defined Constants And Macros *
57*********************************************************************************************************************************/
58#ifdef IN_RT_STATIC /* We don't need full unicode case insensitive if we ASSUME basic latin only. */
59# define RTUniCpToLower(a_uc) ((RTUNICP)RT_C_TO_LOWER(a_uc))
60# define RTUniCpToUpper(a_uc) ((RTUNICP)RT_C_TO_UPPER(a_uc))
61#endif
62
63
64/*********************************************************************************************************************************
65* Global Variables *
66*********************************************************************************************************************************/
67/**
68 * Standard options that gets included unless RTGETOPTINIT_FLAGS_NO_STD_OPTS is
69 * set.
70 */
71static RTGETOPTDEF const g_aStdOptions[] =
72{
73 { "--help", 'h', RTGETOPT_REQ_NOTHING },
74 { "-help", 'h', RTGETOPT_REQ_NOTHING },
75 { "--version", 'V', RTGETOPT_REQ_NOTHING },
76 { "-version", 'V', RTGETOPT_REQ_NOTHING },
77};
78/** The index of --help in g_aStdOptions. Used for some trickery. */
79#define RTGETOPT_STD_OPTIONS_HELP_IDX 0
80
81
82
83RTDECL(int) RTGetOptInit(PRTGETOPTSTATE pState, int argc, char **argv,
84 PCRTGETOPTDEF paOptions, size_t cOptions,
85 int iFirst, uint32_t fFlags)
86{
87 AssertReturn(!(fFlags & ~(RTGETOPTINIT_FLAGS_OPTS_FIRST | RTGETOPTINIT_FLAGS_NO_STD_OPTS)), VERR_INVALID_PARAMETER);
88
89 pState->argv = argv;
90 pState->argc = argc;
91 pState->paOptions = paOptions;
92 pState->cOptions = cOptions;
93 pState->iNext = iFirst;
94 pState->pszNextShort = NULL;
95 pState->pDef = NULL;
96 pState->uIndex = UINT32_MAX;
97 pState->fFlags = fFlags;
98 pState->cNonOptions = 0;
99
100#ifdef RT_STRICT
101 /* validate the options. */
102 for (size_t i = 0; i < cOptions; i++)
103 {
104 Assert(!(paOptions[i].fFlags & ~RTGETOPT_VALID_MASK));
105 Assert( !(paOptions[i].fFlags & (RTGETOPT_FLAG_INDEX_DEF_MASK | RTGETOPT_FLAG_INDEX_DEF_DASH))
106 || (paOptions[i].fFlags & RTGETOPT_FLAG_INDEX) );
107 Assert(paOptions[i].iShort > 0);
108 Assert(paOptions[i].iShort != VINF_GETOPT_NOT_OPTION);
109 Assert(paOptions[i].iShort != '-');
110 if (paOptions[i].fFlags & RTGETOPT_FLAG_ICASE)
111 {
112 const char *psz = paOptions[i].pszLong;
113 unsigned char ch;
114 while ((ch = *psz++) != '\0')
115 Assert(ch <= 0x7f); /* ASSUMPTION that we can use also RTStrICmpAscii and RTStrNICmpAscii for static builds
116 and that we don't need to use RTStrGetCp on pszLong strings. */
117 }
118 }
119#endif
120
121 return VINF_SUCCESS;
122}
123RT_EXPORT_SYMBOL(RTGetOptInit);
124
125#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
126
127/**
128 * Converts an stringified IPv4 address into the RTNETADDRIPV4 representation.
129 *
130 * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
131 * failure.
132 *
133 * @param pszValue The value to convert.
134 * @param pAddr Where to store the result.
135 */
136static int rtgetoptConvertIPv4Addr(const char *pszValue, PRTNETADDRIPV4 pAddr)
137{
138 if (RT_FAILURE(RTNetStrToIPv4Addr(pszValue, pAddr)))
139 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
140 return VINF_SUCCESS;
141}
142
143
144/**
145 * Converts an stringified Ethernet MAC address into the RTMAC representation.
146 *
147 * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
148 * failure.
149 *
150 * @param pszValue The value to convert.
151 * @param pAddr Where to store the result.
152 */
153static int rtgetoptConvertMacAddr(const char *pszValue, PRTMAC pAddr)
154{
155
156 int rc = RTNetStrToMacAddr(pszValue, pAddr);
157 if (RT_FAILURE(rc))
158 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
159
160 return VINF_SUCCESS;
161}
162
163#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
164
165
166/**
167 * Checks if the given unicode codepoint is an alternative dash or not.
168 *
169 * ASSUMES caller checks for the ascii hypen-minus.
170 *
171 * @returns true / false.
172 * @param uc The codepoint to check.
173 */
174DECLINLINE(bool) rtGetOptIsAlternativeDash(RTUNICP uc)
175{
176 return uc == 0x2011 /* non-breaking hypen */
177 || uc == 0x2010 /* hypen */
178 || uc == 0x2012 /* figure dash */
179 || uc == 0x2013 /* en dash */
180 || uc == 0x2014 /* em dash */
181 || uc == 0x2212 /* minus sign */
182 || uc == 0xfe58 /* small em dash */
183 || uc == 0xfe63 /* small em hypen-minus */
184 || uc == 0xff0d /* fullwidth hypen-minus */
185 ;
186}
187
188
189/**
190 * Checks if the given unicode codepoint is an any kind of dash.
191 *
192 * @returns true / false.
193 * @param uc The codepoint to check.
194 */
195DECLINLINE(bool) rtGetOptIsDash(RTUNICP uc)
196{
197 return uc == (RTUNICP)'-' || rtGetOptIsAlternativeDash(uc);
198}
199
200
201/**
202 * Compares a user specific argument with a RTGETOPTDEF long string, taking
203 * RTGETOPT_FLAG_ICASE into account.
204 *
205 * This differs from strcmp/RTStrICmp in that it matches the minus-hyphen
206 * (U+002D) codepoint in a RTGETOPTDEF string with non-breaking hyphen (U+2011)
207 * and some others. This allows copy & paste from manuals, documentation and
208 * similar that needs to use the non-breaking variant for typographical reasons.
209 *
210 * @returns true if matches, false if not.
211 * @param pszUserArg Ther user specified argument string.
212 * @param pszOption The RTGETOPTDEF::pszLong string.
213 * @param fOptFlags The RTGETOPTDEF::fFlags.
214 * @param cchMax RT_STR_MAX if full string compare, otherwise
215 * number of @a pszOption characters to compare.
216 * @param poffUserArgNext Where to return offset of the first character
217 * after the match in @a pszUserArg. Optional.
218 */
219static bool rtGetOptLongStrEquals(const char *pszUserArg, const char *pszOption, size_t cchMax, uint32_t fOptFlags,
220 size_t *poffUserArgNext)
221{
222 const char * const pszUserArgStart = pszUserArg;
223 while (cchMax-- > 0)
224 {
225 char const ch1 = *pszUserArg++;
226 char const ch2 = *pszOption++;
227 if (ch1 == ch2)
228 {
229 if (!ch1)
230 break;
231 }
232 /* If the long option contains a dash, consider alternative unicode dashes
233 like non-breaking and such. */
234 else if (ch2 == '-')
235 {
236 if (!((unsigned char)ch1 & 0x80))
237 return false;
238
239 pszUserArg--;
240 RTUNICP uc;
241 int rc = RTStrGetCpEx(&pszUserArg, &uc);
242 AssertRCReturn(rc, false);
243
244 if (!rtGetOptIsAlternativeDash(uc))
245 return false;
246 }
247 /* Do case folding if configured for this option. */
248 else if (!(fOptFlags & RTGETOPT_FLAG_ICASE))
249 return false;
250 else
251 {
252 pszUserArg--;
253 RTUNICP uc;
254 int rc = RTStrGetCpEx(&pszUserArg, &uc);
255 AssertRCReturn(rc, false);
256
257 if ( RTUniCpToLower(uc) != (RTUNICP)RT_C_TO_LOWER(ch2)
258 && RTUniCpToUpper(uc) != (RTUNICP)RT_C_TO_UPPER(ch2))
259 return false;
260 }
261 }
262 if (poffUserArgNext)
263 *poffUserArgNext = (size_t)(pszUserArg - pszUserArgStart);
264 return true;
265}
266
267
268/**
269 * Skips the optional dash for options with the RTGETOPT_FLAG_INDEX_DEF_DASH
270 * flag.
271 */
272static size_t rtGetOptSkipIndexDefDash(const char *pszOption, size_t cchLong)
273{
274 const char *pszNext = &pszOption[cchLong];
275 RTUNICP uc;
276 int rc = RTStrGetCpEx(&pszNext, &uc);
277 AssertRCReturn(rc, cchLong);
278
279 /* given "--long" we match "--long-1" but not "--long-". */
280 if ( rtGetOptIsDash(uc)
281 && RT_C_IS_DIGIT(*pszNext))
282 return (size_t)(pszNext - pszOption);
283
284 return cchLong;
285}
286
287
288/**
289 * Searches for a long option.
290 *
291 * @returns Pointer to a matching option.
292 * @param pszOption The alleged long option.
293 * @param paOptions Option array.
294 * @param cOptions Number of items in the array.
295 * @param fFlags Init flags.
296 * @param pcchMatch Where to return the length of the matching option
297 * section, including any RTGETOPT_FLAG_INDEX_DEF_DASH
298 * dash, but _not_ the actual index or value separator.
299 */
300static PCRTGETOPTDEF rtGetOptSearchLong(const char *pszOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags,
301 size_t *pcchMatch)
302{
303 PCRTGETOPTDEF pOpt = paOptions;
304 while (cOptions-- > 0)
305 {
306 if (pOpt->pszLong)
307 {
308 uint32_t const fOptFlags = pOpt->fFlags;
309 if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
310 {
311 /*
312 * A value is required with the argument. We're trying to be
313 * understanding here and will permit any of the following:
314 * --long12:value, --long12=value, --long12 value,
315 * --long:value, --long=value, --long value,
316 *
317 * If the option is index, then all trailing chars must be
318 * digits. For error reporting reasons we also match where
319 * there is no index.
320 */
321 size_t cchLong = strlen(pOpt->pszLong);
322 if (rtGetOptLongStrEquals(pszOption, pOpt->pszLong, cchLong, fOptFlags, &cchLong))
323 {
324 if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
325 cchLong = rtGetOptSkipIndexDefDash(pszOption, cchLong);
326 size_t const cchMatch = cchLong;
327 if (fOptFlags & RTGETOPT_FLAG_INDEX)
328 while (RT_C_IS_DIGIT(pszOption[cchLong]))
329 cchLong++;
330 if ( pszOption[cchLong] == '\0'
331 || pszOption[cchLong] == ':'
332 || pszOption[cchLong] == '=')
333 {
334 *pcchMatch = cchMatch;
335 return pOpt;
336 }
337 }
338 }
339 else if (fOptFlags & RTGETOPT_FLAG_INDEX)
340 {
341 /*
342 * The option takes an index but no value.
343 * As above, we also match where there is no index.
344 */
345 size_t cchLong = strlen(pOpt->pszLong);
346 if (rtGetOptLongStrEquals(pszOption, pOpt->pszLong, cchLong, fOptFlags, &cchLong))
347 {
348 if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
349 cchLong = rtGetOptSkipIndexDefDash(pszOption, cchLong);
350 size_t const cchMatch = cchLong;
351 while (RT_C_IS_DIGIT(pszOption[cchLong]))
352 cchLong++;
353 if (pszOption[cchLong] == '\0')
354 {
355 *pcchMatch = cchMatch;
356 return pOpt;
357 }
358 }
359 }
360 else if (rtGetOptLongStrEquals(pszOption, pOpt->pszLong, RTSTR_MAX, fOptFlags, pcchMatch))
361 return pOpt;
362 }
363 pOpt++;
364 }
365
366 if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
367 for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
368 {
369 if (rtGetOptLongStrEquals(pszOption, g_aStdOptions[i].pszLong, RTSTR_MAX, g_aStdOptions[i].fFlags, pcchMatch))
370 return &g_aStdOptions[i];
371 }
372
373 *pcchMatch = 0;
374 return NULL;
375}
376
377
378/**
379 * Searches for a matching short option.
380 *
381 * @returns Pointer to a matching option.
382 * @param chOption The option char.
383 * @param paOptions Option array.
384 * @param cOptions Number of items in the array.
385 * @param fFlags Init flags.
386 */
387static PCRTGETOPTDEF rtGetOptSearchShort(int chOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
388{
389 PCRTGETOPTDEF pOpt = paOptions;
390 while (cOptions-- > 0)
391 {
392 if (pOpt->iShort == chOption)
393 return pOpt;
394 pOpt++;
395 }
396
397 if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
398 {
399 for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
400 if (g_aStdOptions[i].iShort == chOption)
401 return &g_aStdOptions[i];
402 if (chOption == '?')
403 return &g_aStdOptions[RTGETOPT_STD_OPTIONS_HELP_IDX];
404 }
405 return NULL;
406}
407
408
409/**
410 * Value string -> Value union.
411 *
412 * @returns IPRT status code.
413 * @param fFlags The value flags.
414 * @param pszValue The value string.
415 * @param pValueUnion Where to return the processed value.
416 */
417static int rtGetOptProcessValue(uint32_t fFlags, const char *pszValue, PRTGETOPTUNION pValueUnion)
418{
419 /*
420 * Transform into a option value as requested.
421 * If decimal conversion fails, we'll check for "0x<xdigit>" and
422 * try a 16 based conversion. We will not interpret any of the
423 * generic ints as octals.
424 */
425 uint32_t const fSwitchValue = fFlags & ( RTGETOPT_REQ_MASK
426 | RTGETOPT_FLAG_HEX
427 | RTGETOPT_FLAG_DEC
428 | RTGETOPT_FLAG_OCT);
429 switch (fSwitchValue)
430 {
431 case RTGETOPT_REQ_STRING:
432 pValueUnion->psz = pszValue;
433 break;
434
435 case RTGETOPT_REQ_BOOL:
436 {
437 static struct RTGETOPTUPPERLOWERBOOL
438 {
439 bool f;
440 const char *pszLower;
441 const char *pszUpper;
442 const char *pszMarkers; /**< characters with '+' are okay to stop after, '-' aren't.*/
443 } const s_aKeywords[] =
444 {
445 { true, "true", "TRUE", "+--+" },
446 { true, "yes", "YES", "+-+" },
447 { true, "enabled", "ENABLED", "++---++" },
448 { true, "on", "ON", "-+" },
449 { true, "1", "1", "+" },
450 { false, "false", "FALSE", "+---+" },
451 { false, "no", "NO", "++" },
452 { false, "disabled", "DISABLED", "+-+---++" },
453 { false, "off", "OFF", "--+" },
454 { false, "0", "0", "+" },
455 };
456 for (size_t i = 0; i < RT_ELEMENTS(s_aKeywords); i++)
457 for (size_t off = 0;; off++)
458 {
459 char const ch = pszValue[off];
460 if (!ch)
461 {
462 if (off > 0 && s_aKeywords[i].pszMarkers[off - 1] == '+')
463 {
464 pValueUnion->f = s_aKeywords[i].f;
465 return VINF_SUCCESS;
466 }
467 break;
468 }
469 if ( s_aKeywords[i].pszLower[off] != ch
470 && s_aKeywords[i].pszUpper[off] != ch)
471 break;
472 }
473 pValueUnion->psz = pszValue;
474 return VERR_GETOPT_UNKNOWN_OPTION;
475 }
476
477 case RTGETOPT_REQ_BOOL_ONOFF:
478 if ( (pszValue[0] == 'o' || pszValue[0] == 'O')
479 && (pszValue[1] == 'n' || pszValue[1] == 'N')
480 && pszValue[2] == '\0')
481 pValueUnion->f = true;
482 else if ( (pszValue[0] == 'o' || pszValue[0] == 'O')
483 && (pszValue[1] == 'f' || pszValue[1] == 'F')
484 && (pszValue[2] == 'f' || pszValue[2] == 'F')
485 && pszValue[3] == '\0')
486 pValueUnion->f = false;
487 else
488 {
489 pValueUnion->psz = pszValue;
490 return VERR_GETOPT_UNKNOWN_OPTION;
491 }
492 break;
493
494#define MY_INT_CASE(req, type, memb, convfn) \
495 case req: \
496 { \
497 type Value; \
498 if ( convfn(pszValue, 10, &Value) != VINF_SUCCESS \
499 && ( pszValue[0] != '0' \
500 || (pszValue[1] != 'x' && pszValue[1] != 'X') \
501 || !RT_C_IS_XDIGIT(pszValue[2]) \
502 || convfn(pszValue, 16, &Value) != VINF_SUCCESS ) ) \
503 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
504 pValueUnion->memb = Value; \
505 break; \
506 }
507#define MY_BASE_INT_CASE(req, type, memb, convfn, base) \
508 case req: \
509 { \
510 type Value; \
511 if (convfn(pszValue, base, &Value) != VINF_SUCCESS) \
512 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
513 pValueUnion->memb = Value; \
514 break; \
515 }
516
517 MY_INT_CASE(RTGETOPT_REQ_INT8, int8_t, i8, RTStrToInt8Full)
518 MY_INT_CASE(RTGETOPT_REQ_INT16, int16_t, i16, RTStrToInt16Full)
519 MY_INT_CASE(RTGETOPT_REQ_INT32, int32_t, i32, RTStrToInt32Full)
520 MY_INT_CASE(RTGETOPT_REQ_INT64, int64_t, i64, RTStrToInt64Full)
521 MY_INT_CASE(RTGETOPT_REQ_UINT8, uint8_t, u8, RTStrToUInt8Full)
522 MY_INT_CASE(RTGETOPT_REQ_UINT16, uint16_t, u16, RTStrToUInt16Full)
523 MY_INT_CASE(RTGETOPT_REQ_UINT32, uint32_t, u32, RTStrToUInt32Full)
524 MY_INT_CASE(RTGETOPT_REQ_UINT64, uint64_t, u64, RTStrToUInt64Full)
525
526 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_HEX, int8_t, i8, RTStrToInt8Full, 16)
527 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_HEX, int16_t, i16, RTStrToInt16Full, 16)
528 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_HEX, int32_t, i32, RTStrToInt32Full, 16)
529 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_HEX, int64_t, i64, RTStrToInt64Full, 16)
530 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_HEX, uint8_t, u8, RTStrToUInt8Full, 16)
531 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_HEX, uint16_t, u16, RTStrToUInt16Full, 16)
532 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX, uint32_t, u32, RTStrToUInt32Full, 16)
533 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX, uint64_t, u64, RTStrToUInt64Full, 16)
534
535 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_DEC, int8_t, i8, RTStrToInt8Full, 10)
536 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_DEC, int16_t, i16, RTStrToInt16Full, 10)
537 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_DEC, int32_t, i32, RTStrToInt32Full, 10)
538 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_DEC, int64_t, i64, RTStrToInt64Full, 10)
539 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_DEC, uint8_t, u8, RTStrToUInt8Full, 10)
540 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_DEC, uint16_t, u16, RTStrToUInt16Full, 10)
541 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_DEC, uint32_t, u32, RTStrToUInt32Full, 10)
542 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_DEC, uint64_t, u64, RTStrToUInt64Full, 10)
543
544 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_OCT, int8_t, i8, RTStrToInt8Full, 8)
545 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_OCT, int16_t, i16, RTStrToInt16Full, 8)
546 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_OCT, int32_t, i32, RTStrToInt32Full, 8)
547 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_OCT, int64_t, i64, RTStrToInt64Full, 8)
548 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_OCT, uint8_t, u8, RTStrToUInt8Full, 8)
549 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_OCT, uint16_t, u16, RTStrToUInt16Full, 8)
550 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT, uint32_t, u32, RTStrToUInt32Full, 8)
551 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_OCT, uint64_t, u64, RTStrToUInt64Full, 8)
552
553#undef MY_INT_CASE
554#undef MY_BASE_INT_CASE
555
556#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
557
558 case RTGETOPT_REQ_IPV4ADDR:
559 {
560 RTNETADDRIPV4 Addr;
561 if (rtgetoptConvertIPv4Addr(pszValue, &Addr) != VINF_SUCCESS)
562 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
563 pValueUnion->IPv4Addr = Addr;
564 break;
565 }
566
567 case RTGETOPT_REQ_IPV4CIDR:
568 {
569 RTNETADDRIPV4 network;
570 RTNETADDRIPV4 netmask;
571 if (RT_FAILURE(RTCidrStrToIPv4(pszValue, &network, &netmask)))
572 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
573 pValueUnion->CidrIPv4.IPv4Network.u = network.u;
574 pValueUnion->CidrIPv4.IPv4Netmask.u = netmask.u;
575 break;
576 }
577
578 case RTGETOPT_REQ_MACADDR:
579 {
580 RTMAC Addr;
581 if (rtgetoptConvertMacAddr(pszValue, &Addr) != VINF_SUCCESS)
582 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
583 pValueUnion->MacAddr = Addr;
584 break;
585 }
586
587#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
588
589 case RTGETOPT_REQ_UUID:
590 {
591 RTUUID Uuid;
592 if (RTUuidFromStr(&Uuid, pszValue) != VINF_SUCCESS)
593 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
594 pValueUnion->Uuid = Uuid;
595 break;
596 }
597
598#define MY_INT_PAIR_CASE(a_fReqValue, a_fReqValueOptional, a_Type, a_MemberPrefix, a_fnConv, a_ConvBase, a_DefaultValue) \
599 case a_fReqValue: \
600 case a_fReqValueOptional: \
601 { \
602 /* First value: */ \
603 a_Type Value1; \
604 char *pszNext = NULL; \
605 unsigned uBase = pszValue[0] == '0' \
606 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
607 && RT_C_IS_XDIGIT(pszValue[2]) \
608 ? 16 : a_ConvBase; \
609 int rc = a_fnConv(pszValue, &pszNext, uBase, &Value1); \
610 if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES) \
611 { \
612 /* The second value, could be optional: */ \
613 a_Type Value2 = a_DefaultValue; \
614 pszValue = pszNext;\
615 if (pszValue) \
616 { \
617 while (RT_C_IS_BLANK(*pszValue)) \
618 pszValue++; \
619 if (*pszValue == ':' || *pszValue == '/' || *pszValue == '|') \
620 do pszValue++; \
621 while (RT_C_IS_BLANK(*pszValue)); \
622 if (pszValue != pszNext) \
623 { \
624 uBase = pszValue[0] == '0' \
625 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
626 && RT_C_IS_XDIGIT(pszValue[2]) \
627 ? 16 : a_ConvBase; \
628 rc = a_fnConv(pszValue, &pszNext, uBase, &Value2); \
629 if (rc == VINF_SUCCESS) \
630 { /* likely */ } \
631 else \
632 AssertMsgFailedReturn(("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase), \
633 VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
634 } \
635 else if (fSwitchValue != (a_fReqValueOptional)) \
636 AssertMsgFailedReturn(("x\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
637 } \
638 else if (fSwitchValue != (a_fReqValueOptional)) \
639 AssertMsgFailedReturn(("y\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
640 pValueUnion->a_MemberPrefix##Second = Value2; \
641 pValueUnion->a_MemberPrefix##First = Value1; \
642 break; \
643 } \
644 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
645 }
646
647 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR,
648 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
649 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
650 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
651 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
652 uint32_t, PairU32.u, RTStrToUInt32Ex, 16, UINT32_MAX)
653 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
654 uint32_t, PairU32.u, RTStrToUInt32Ex, 8, UINT32_MAX)
655
656 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR,
657 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
658 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
659 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
660 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
661 uint64_t, PairU64.u, RTStrToUInt64Ex, 16, UINT64_MAX)
662 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
663 uint64_t, PairU64.u, RTStrToUInt64Ex, 8, UINT64_MAX)
664
665 default:
666 AssertMsgFailed(("f=%#x\n", fFlags));
667 return VERR_INTERNAL_ERROR;
668 }
669
670 return VINF_SUCCESS;
671}
672
673
674/**
675 * Moves one argv option entries.
676 *
677 * @param papszTo Destination.
678 * @param papszFrom Source.
679 */
680static void rtGetOptMoveArgvEntries(char **papszTo, char **papszFrom)
681{
682 if (papszTo != papszFrom)
683 {
684 Assert((uintptr_t)papszTo < (uintptr_t)papszFrom);
685 char * const pszMoved = papszFrom[0];
686 memmove(&papszTo[1], &papszTo[0], (uintptr_t)papszFrom - (uintptr_t)papszTo);
687 papszTo[0] = pszMoved;
688 }
689}
690
691
692RTDECL(int) RTGetOpt(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion)
693{
694 /*
695 * Reset the variables kept in state.
696 */
697 pState->pDef = NULL;
698 pState->uIndex = UINT32_MAX;
699
700 /*
701 * Make sure the union is completely cleared out, whatever happens below.
702 */
703 pValueUnion->u64 = 0;
704 pValueUnion->pDef = NULL;
705
706 /*
707 * The next option.
708 */
709 bool fShort;
710 int iThis;
711 const char *pszArgThis;
712 size_t cchMatch;
713 PCRTGETOPTDEF pOpt;
714
715 if (pState->pszNextShort)
716 {
717 /*
718 * We've got short options left over from the previous call.
719 */
720 pOpt = rtGetOptSearchShort(*pState->pszNextShort, pState->paOptions, pState->cOptions, pState->fFlags);
721 if (!pOpt)
722 {
723 pValueUnion->psz = pState->pszNextShort;
724 return VERR_GETOPT_UNKNOWN_OPTION;
725 }
726 pszArgThis = pState->pszNextShort++;
727 cchMatch = 1;
728 iThis = pState->iNext;
729 fShort = true;
730 }
731 else
732 {
733 /*
734 * Pop off the next argument. Sorting options and dealing with the
735 * dash-dash makes this a little extra complicated.
736 */
737 for (;;)
738 {
739 if (pState->iNext >= pState->argc)
740 return 0;
741
742 if (pState->cNonOptions)
743 {
744 if (pState->cNonOptions == INT32_MAX)
745 {
746 pValueUnion->psz = pState->argv[pState->iNext++];
747 return VINF_GETOPT_NOT_OPTION;
748 }
749
750 if (pState->iNext + pState->cNonOptions >= pState->argc)
751 {
752 pState->cNonOptions = INT32_MAX;
753 continue;
754 }
755 }
756
757 iThis = pState->iNext++;
758 pszArgThis = pState->argv[iThis + pState->cNonOptions];
759
760 /*
761 * Do a long option search first and then a short option one.
762 * This way we can make sure single dash long options doesn't
763 * get mixed up with short ones.
764 */
765 pOpt = rtGetOptSearchLong(pszArgThis, pState->paOptions, pState->cOptions, pState->fFlags, &cchMatch);
766 fShort = false;
767 if (!pOpt)
768 {
769 const char *pszNext = pszArgThis;
770 RTUNICP uc;
771 int vrc = RTStrGetCpEx(&pszNext, &uc);
772 AssertRCReturn(vrc, vrc);
773 char chNext;
774 if ( rtGetOptIsDash(uc)
775 && (chNext = *pszNext) != '-'
776 && chNext != '\0'
777 && !((unsigned char)chNext & 0x80))
778 {
779 pOpt = rtGetOptSearchShort(chNext, pState->paOptions, pState->cOptions, pState->fFlags);
780 if (pOpt)
781 {
782 cchMatch = (size_t)(&pszNext[1] - pszArgThis);
783 fShort = true;
784 }
785 }
786 }
787
788 /* Look for dash-dash. */
789 if (!pOpt && rtGetOptLongStrEquals(pszArgThis, "--", RTSTR_MAX, 0, NULL))
790 {
791 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
792 pState->cNonOptions = INT32_MAX;
793 continue;
794 }
795
796 /* Options first hacks. */
797 if (pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST)
798 {
799 if (pOpt)
800 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
801 else if (rtGetOptIsDash(RTStrGetCp(pszArgThis)))
802 {
803 pValueUnion->psz = pszArgThis;
804 return VERR_GETOPT_UNKNOWN_OPTION;
805 }
806 else
807 {
808 /* not an option, add it to the non-options and try again. */
809 pState->iNext--;
810 pState->cNonOptions++;
811
812 /* Switch to returning non-options if we've reached the end. */
813 if (pState->iNext + pState->cNonOptions >= pState->argc)
814 pState->cNonOptions = INT32_MAX;
815 continue;
816 }
817 }
818
819 /* done */
820 break;
821 }
822 }
823
824 if (pOpt)
825 {
826 pValueUnion->pDef = pOpt; /* in case of no value or error. */
827
828 uint32_t const fOptFlags = pOpt->fFlags;
829 if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
830 {
831 /*
832 * Find the argument value.
833 *
834 * A value is required with the argument. We're trying to be
835 * understanding here and will permit any of the following:
836 * -svalue, -s value, -s:value and -s=value
837 * (Ditto for long options.)
838 */
839 const char *pszValue;
840 if (fShort)
841 {
842 if (pszArgThis[cchMatch] == '\0')
843 {
844 if (iThis + 1 >= pState->argc)
845 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
846 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
847 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
848 pState->iNext++;
849 }
850 else /* same argument. */
851 pszValue = &pszArgThis[cchMatch + (pszArgThis[cchMatch] == ':' || pszArgThis[cchMatch] == '=')];
852 if (pState->pszNextShort)
853 {
854 pState->pszNextShort = NULL;
855 pState->iNext++;
856 }
857 }
858 else
859 {
860 if (fOptFlags & RTGETOPT_FLAG_INDEX)
861 {
862 if ( pszArgThis[cchMatch] != '\0'
863 || (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
864 {
865 const char *pszNext = &pszArgThis[cchMatch];
866 uint32_t uIndex;
867 int rc = RTStrToUInt32Ex(pszNext, (char **)&pszNext, 10, &uIndex);
868 if ( rc == VERR_NO_DIGITS
869 && (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
870 {
871 uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
872 rc = pszNext[0] == '\0' ? VINF_SUCCESS : VWRN_TRAILING_CHARS;
873 }
874 if (rc == VWRN_TRAILING_CHARS)
875 {
876 if ( pszNext[0] != ':'
877 && pszNext[0] != '=')
878 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
879 pState->uIndex = uIndex;
880 pszValue = pszNext + 1;
881 }
882 else if (rc == VINF_SUCCESS)
883 {
884 if (iThis + 1 + pState->cNonOptions >= pState->argc)
885 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
886 pState->uIndex = uIndex;
887 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
888 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
889 pState->iNext++;
890 }
891 else
892 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
893 }
894 else
895 return VERR_GETOPT_INDEX_MISSING;
896 }
897 else
898 {
899 if (pszArgThis[cchMatch] == '\0')
900 {
901 if (iThis + 1 + pState->cNonOptions >= pState->argc)
902 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
903 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
904 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
905 pState->iNext++;
906 }
907 else /* same argument. */
908 pszValue = &pszArgThis[cchMatch + 1];
909 }
910 }
911
912 /*
913 * Set up the ValueUnion.
914 */
915 int rc = rtGetOptProcessValue(fOptFlags, pszValue, pValueUnion);
916 if (RT_FAILURE(rc))
917 return rc;
918 }
919 else if (fShort)
920 {
921 /*
922 * Deal with "compressed" short option lists, correcting the next
923 * state variables for the start and end cases.
924 */
925 if (pszArgThis[cchMatch])
926 {
927 if (!pState->pszNextShort)
928 {
929 /* start */
930 pState->pszNextShort = &pszArgThis[cchMatch];
931 pState->iNext--;
932 }
933 }
934 else if (pState->pszNextShort)
935 {
936 /* end */
937 pState->pszNextShort = NULL;
938 pState->iNext++;
939 }
940 }
941 else if (fOptFlags & RTGETOPT_FLAG_INDEX)
942 {
943 uint32_t uIndex;
944 if (pszArgThis[cchMatch] != '\0')
945 {
946 if (RTStrToUInt32Full(&pszArgThis[cchMatch], 10, &uIndex) == VINF_SUCCESS)
947 pState->uIndex = uIndex;
948 else
949 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
950 }
951 else if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK)
952 uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
953 else
954 return VERR_GETOPT_INDEX_MISSING;
955 }
956
957 pState->pDef = pOpt;
958 return pOpt->iShort;
959 }
960
961 /*
962 * Not a known option argument. If it starts with a switch char (-) we'll
963 * fail with unknown option, and if it doesn't we'll return it as a non-option.
964 */
965 if (*pszArgThis == '-')
966 {
967 pValueUnion->psz = pszArgThis;
968 return VERR_GETOPT_UNKNOWN_OPTION;
969 }
970
971 pValueUnion->psz = pszArgThis;
972 return VINF_GETOPT_NOT_OPTION;
973}
974RT_EXPORT_SYMBOL(RTGetOpt);
975
976
977RTDECL(int) RTGetOptFetchValue(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion, uint32_t fFlags)
978{
979 /*
980 * Validate input.
981 */
982 PCRTGETOPTDEF pOpt = pState->pDef;
983 AssertReturn(!(fFlags & ~RTGETOPT_VALID_MASK), VERR_INVALID_PARAMETER);
984 AssertReturn((fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING, VERR_INVALID_PARAMETER);
985
986 /*
987 * Make sure the union is completely cleared out, whatever happens below.
988 */
989 pValueUnion->u64 = 0;
990 pValueUnion->pDef = NULL;
991
992 /*
993 * Pop off the next argument and convert it into a value union.
994 */
995 if (pState->iNext >= pState->argc)
996 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
997 int iThis = pState->iNext++;
998 const char *pszValue = pState->argv[iThis + (pState->cNonOptions != INT32_MAX ? pState->cNonOptions : 0)];
999 pValueUnion->pDef = pOpt; /* in case of no value or error. */
1000
1001 if (pState->cNonOptions && pState->cNonOptions != INT32_MAX)
1002 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
1003
1004 return rtGetOptProcessValue(fFlags, pszValue, pValueUnion);
1005}
1006RT_EXPORT_SYMBOL(RTGetOptFetchValue);
1007
1008
1009RTDECL(char **) RTGetOptNonOptionArrayPtr(PRTGETOPTSTATE pState)
1010{
1011 AssertReturn(pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST, NULL);
1012 return &pState->argv[pState->iNext - 1];
1013}
1014RT_EXPORT_SYMBOL(RTGetOptNonOptionArrayPtr);
1015
1016
1017RTDECL(RTEXITCODE) RTGetOptPrintError(int ch, PCRTGETOPTUNION pValueUnion)
1018{
1019 if (ch == VINF_GETOPT_NOT_OPTION)
1020 RTMsgError("Invalid parameter: %s", pValueUnion->psz);
1021 else if (ch > 0)
1022 {
1023 if (RT_C_IS_GRAPH(ch))
1024 RTMsgError("Unhandled option: -%c", ch);
1025 else
1026 RTMsgError("Unhandled option: %i (%#x)", ch, ch);
1027 }
1028 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1029 RTMsgError("Unknown option: '%s'", pValueUnion->psz);
1030 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
1031 /** @todo r=klaus not really ideal, as the value isn't available */
1032 RTMsgError("The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
1033 else if (pValueUnion->pDef)
1034 RTMsgError("%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
1035 else
1036 RTMsgError("%Rrs\n", ch);
1037
1038 return RTEXITCODE_SYNTAX;
1039}
1040RT_EXPORT_SYMBOL(RTGetOptPrintError);
1041
1042
1043RTDECL(ssize_t) RTGetOptFormatError(char *pszBuf, size_t cbBuf, int ch, PCRTGETOPTUNION pValueUnion)
1044{
1045 ssize_t cchRet;
1046 if (ch == VINF_GETOPT_NOT_OPTION)
1047 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Invalid parameter: %s", pValueUnion->psz);
1048 else if (ch > 0)
1049 {
1050 if (RT_C_IS_GRAPH(ch))
1051 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: -%c", ch);
1052 else
1053 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: %i (%#x)", ch, ch);
1054 }
1055 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1056 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unknown option: '%s'", pValueUnion->psz);
1057 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
1058 /** @todo r=klaus not really ideal, as the value isn't available */
1059 cchRet = RTStrPrintf2(pszBuf, cbBuf, "The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
1060 else if (pValueUnion->pDef)
1061 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
1062 else
1063 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%Rrs\n", ch);
1064
1065 return cchRet;
1066}
1067RT_EXPORT_SYMBOL(RTGetOptFormatError);
1068
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use