VirtualBox

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

Last change on this file since 99248 was 99248, checked in by vboxsync, 21 months ago

IPRT/RTGetOpt: Interpret non-breaking hypen and a handful other unicode dashes like the asci '-' when parsing command line, so that the manual, blog entries and such are free to use other dash variants for typesetting reasons w/o prevent the reader from copying & pasting the examples directly onto the command line. [build fix] bugref:10302

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 42.0 KB
Line 
1/* $Id: getopt.cpp 99248 2023-03-31 09:02:48Z 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 size_t cchMatch = 0;
370 if (rtGetOptLongStrEquals(pszOption, g_aStdOptions[i].pszLong, RTSTR_MAX, g_aStdOptions[i].fFlags, &cchMatch))
371 {
372 *pcchMatch = cchMatch;
373 return &g_aStdOptions[i];
374 }
375 }
376
377 *pcchMatch = 0;
378 return NULL;
379}
380
381
382/**
383 * Searches for a matching short option.
384 *
385 * @returns Pointer to a matching option.
386 * @param chOption The option char.
387 * @param paOptions Option array.
388 * @param cOptions Number of items in the array.
389 * @param fFlags Init flags.
390 */
391static PCRTGETOPTDEF rtGetOptSearchShort(int chOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
392{
393 PCRTGETOPTDEF pOpt = paOptions;
394 while (cOptions-- > 0)
395 {
396 if (pOpt->iShort == chOption)
397 return pOpt;
398 pOpt++;
399 }
400
401 if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
402 {
403 for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
404 if (g_aStdOptions[i].iShort == chOption)
405 return &g_aStdOptions[i];
406 if (chOption == '?')
407 return &g_aStdOptions[RTGETOPT_STD_OPTIONS_HELP_IDX];
408 }
409 return NULL;
410}
411
412
413/**
414 * Value string -> Value union.
415 *
416 * @returns IPRT status code.
417 * @param fFlags The value flags.
418 * @param pszValue The value string.
419 * @param pValueUnion Where to return the processed value.
420 */
421static int rtGetOptProcessValue(uint32_t fFlags, const char *pszValue, PRTGETOPTUNION pValueUnion)
422{
423 /*
424 * Transform into a option value as requested.
425 * If decimal conversion fails, we'll check for "0x<xdigit>" and
426 * try a 16 based conversion. We will not interpret any of the
427 * generic ints as octals.
428 */
429 uint32_t const fSwitchValue = fFlags & ( RTGETOPT_REQ_MASK
430 | RTGETOPT_FLAG_HEX
431 | RTGETOPT_FLAG_DEC
432 | RTGETOPT_FLAG_OCT);
433 switch (fSwitchValue)
434 {
435 case RTGETOPT_REQ_STRING:
436 pValueUnion->psz = pszValue;
437 break;
438
439 case RTGETOPT_REQ_BOOL:
440 {
441 static struct RTGETOPTUPPERLOWERBOOL
442 {
443 bool f;
444 const char *pszLower;
445 const char *pszUpper;
446 const char *pszMarkers; /**< characters with '+' are okay to stop after, '-' aren't.*/
447 } const s_aKeywords[] =
448 {
449 { true, "true", "TRUE", "+--+" },
450 { true, "yes", "YES", "+-+" },
451 { true, "enabled", "ENABLED", "++---++" },
452 { true, "on", "ON", "-+" },
453 { true, "1", "1", "+" },
454 { false, "false", "FALSE", "+---+" },
455 { false, "no", "NO", "++" },
456 { false, "disabled", "DISABLED", "+-+---++" },
457 { false, "off", "OFF", "--+" },
458 { false, "0", "0", "+" },
459 };
460 for (size_t i = 0; i < RT_ELEMENTS(s_aKeywords); i++)
461 for (size_t off = 0;; off++)
462 {
463 char const ch = pszValue[off];
464 if (!ch)
465 {
466 if (off > 0 && s_aKeywords[i].pszMarkers[off - 1] == '+')
467 {
468 pValueUnion->f = s_aKeywords[i].f;
469 return VINF_SUCCESS;
470 }
471 break;
472 }
473 if ( s_aKeywords[i].pszLower[off] != ch
474 && s_aKeywords[i].pszUpper[off] != ch)
475 break;
476 }
477 pValueUnion->psz = pszValue;
478 return VERR_GETOPT_UNKNOWN_OPTION;
479 }
480
481 case RTGETOPT_REQ_BOOL_ONOFF:
482 if ( (pszValue[0] == 'o' || pszValue[0] == 'O')
483 && (pszValue[1] == 'n' || pszValue[1] == 'N')
484 && pszValue[2] == '\0')
485 pValueUnion->f = true;
486 else if ( (pszValue[0] == 'o' || pszValue[0] == 'O')
487 && (pszValue[1] == 'f' || pszValue[1] == 'F')
488 && (pszValue[2] == 'f' || pszValue[2] == 'F')
489 && pszValue[3] == '\0')
490 pValueUnion->f = false;
491 else
492 {
493 pValueUnion->psz = pszValue;
494 return VERR_GETOPT_UNKNOWN_OPTION;
495 }
496 break;
497
498#define MY_INT_CASE(req, type, memb, convfn) \
499 case req: \
500 { \
501 type Value; \
502 if ( convfn(pszValue, 10, &Value) != VINF_SUCCESS \
503 && ( pszValue[0] != '0' \
504 || (pszValue[1] != 'x' && pszValue[1] != 'X') \
505 || !RT_C_IS_XDIGIT(pszValue[2]) \
506 || convfn(pszValue, 16, &Value) != VINF_SUCCESS ) ) \
507 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
508 pValueUnion->memb = Value; \
509 break; \
510 }
511#define MY_BASE_INT_CASE(req, type, memb, convfn, base) \
512 case req: \
513 { \
514 type Value; \
515 if (convfn(pszValue, base, &Value) != VINF_SUCCESS) \
516 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
517 pValueUnion->memb = Value; \
518 break; \
519 }
520
521 MY_INT_CASE(RTGETOPT_REQ_INT8, int8_t, i8, RTStrToInt8Full)
522 MY_INT_CASE(RTGETOPT_REQ_INT16, int16_t, i16, RTStrToInt16Full)
523 MY_INT_CASE(RTGETOPT_REQ_INT32, int32_t, i32, RTStrToInt32Full)
524 MY_INT_CASE(RTGETOPT_REQ_INT64, int64_t, i64, RTStrToInt64Full)
525 MY_INT_CASE(RTGETOPT_REQ_UINT8, uint8_t, u8, RTStrToUInt8Full)
526 MY_INT_CASE(RTGETOPT_REQ_UINT16, uint16_t, u16, RTStrToUInt16Full)
527 MY_INT_CASE(RTGETOPT_REQ_UINT32, uint32_t, u32, RTStrToUInt32Full)
528 MY_INT_CASE(RTGETOPT_REQ_UINT64, uint64_t, u64, RTStrToUInt64Full)
529
530 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_HEX, int8_t, i8, RTStrToInt8Full, 16)
531 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_HEX, int16_t, i16, RTStrToInt16Full, 16)
532 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_HEX, int32_t, i32, RTStrToInt32Full, 16)
533 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_HEX, int64_t, i64, RTStrToInt64Full, 16)
534 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_HEX, uint8_t, u8, RTStrToUInt8Full, 16)
535 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_HEX, uint16_t, u16, RTStrToUInt16Full, 16)
536 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX, uint32_t, u32, RTStrToUInt32Full, 16)
537 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX, uint64_t, u64, RTStrToUInt64Full, 16)
538
539 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_DEC, int8_t, i8, RTStrToInt8Full, 10)
540 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_DEC, int16_t, i16, RTStrToInt16Full, 10)
541 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_DEC, int32_t, i32, RTStrToInt32Full, 10)
542 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_DEC, int64_t, i64, RTStrToInt64Full, 10)
543 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_DEC, uint8_t, u8, RTStrToUInt8Full, 10)
544 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_DEC, uint16_t, u16, RTStrToUInt16Full, 10)
545 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_DEC, uint32_t, u32, RTStrToUInt32Full, 10)
546 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_DEC, uint64_t, u64, RTStrToUInt64Full, 10)
547
548 MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_OCT, int8_t, i8, RTStrToInt8Full, 8)
549 MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_OCT, int16_t, i16, RTStrToInt16Full, 8)
550 MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_OCT, int32_t, i32, RTStrToInt32Full, 8)
551 MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_OCT, int64_t, i64, RTStrToInt64Full, 8)
552 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_OCT, uint8_t, u8, RTStrToUInt8Full, 8)
553 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_OCT, uint16_t, u16, RTStrToUInt16Full, 8)
554 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT, uint32_t, u32, RTStrToUInt32Full, 8)
555 MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_OCT, uint64_t, u64, RTStrToUInt64Full, 8)
556
557#undef MY_INT_CASE
558#undef MY_BASE_INT_CASE
559
560#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
561
562 case RTGETOPT_REQ_IPV4ADDR:
563 {
564 RTNETADDRIPV4 Addr;
565 if (rtgetoptConvertIPv4Addr(pszValue, &Addr) != VINF_SUCCESS)
566 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
567 pValueUnion->IPv4Addr = Addr;
568 break;
569 }
570
571 case RTGETOPT_REQ_IPV4CIDR:
572 {
573 RTNETADDRIPV4 network;
574 RTNETADDRIPV4 netmask;
575 if (RT_FAILURE(RTCidrStrToIPv4(pszValue, &network, &netmask)))
576 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
577 pValueUnion->CidrIPv4.IPv4Network.u = network.u;
578 pValueUnion->CidrIPv4.IPv4Netmask.u = netmask.u;
579 break;
580 }
581
582 case RTGETOPT_REQ_MACADDR:
583 {
584 RTMAC Addr;
585 if (rtgetoptConvertMacAddr(pszValue, &Addr) != VINF_SUCCESS)
586 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
587 pValueUnion->MacAddr = Addr;
588 break;
589 }
590
591#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
592
593 case RTGETOPT_REQ_UUID:
594 {
595 RTUUID Uuid;
596 if (RTUuidFromStr(&Uuid, pszValue) != VINF_SUCCESS)
597 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
598 pValueUnion->Uuid = Uuid;
599 break;
600 }
601
602#define MY_INT_PAIR_CASE(a_fReqValue, a_fReqValueOptional, a_Type, a_MemberPrefix, a_fnConv, a_ConvBase, a_DefaultValue) \
603 case a_fReqValue: \
604 case a_fReqValueOptional: \
605 { \
606 /* First value: */ \
607 a_Type Value1; \
608 char *pszNext = NULL; \
609 unsigned uBase = pszValue[0] == '0' \
610 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
611 && RT_C_IS_XDIGIT(pszValue[2]) \
612 ? 16 : a_ConvBase; \
613 int rc = a_fnConv(pszValue, &pszNext, uBase, &Value1); \
614 if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES) \
615 { \
616 /* The second value, could be optional: */ \
617 a_Type Value2 = a_DefaultValue; \
618 pszValue = pszNext;\
619 if (pszValue) \
620 { \
621 while (RT_C_IS_BLANK(*pszValue)) \
622 pszValue++; \
623 if (*pszValue == ':' || *pszValue == '/' || *pszValue == '|') \
624 do pszValue++; \
625 while (RT_C_IS_BLANK(*pszValue)); \
626 if (pszValue != pszNext) \
627 { \
628 uBase = pszValue[0] == '0' \
629 && (pszValue[1] == 'x' || pszValue[1] == 'X') \
630 && RT_C_IS_XDIGIT(pszValue[2]) \
631 ? 16 : a_ConvBase; \
632 rc = a_fnConv(pszValue, &pszNext, uBase, &Value2); \
633 if (rc == VINF_SUCCESS) \
634 { /* likely */ } \
635 else \
636 AssertMsgFailedReturn(("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase), \
637 VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
638 } \
639 else if (fSwitchValue != (a_fReqValueOptional)) \
640 AssertMsgFailedReturn(("x\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
641 } \
642 else if (fSwitchValue != (a_fReqValueOptional)) \
643 AssertMsgFailedReturn(("y\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
644 pValueUnion->a_MemberPrefix##Second = Value2; \
645 pValueUnion->a_MemberPrefix##First = Value1; \
646 break; \
647 } \
648 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
649 }
650
651 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR,
652 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
653 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
654 uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
655 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
656 uint32_t, PairU32.u, RTStrToUInt32Ex, 16, UINT32_MAX)
657 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
658 uint32_t, PairU32.u, RTStrToUInt32Ex, 8, UINT32_MAX)
659
660 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR,
661 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
662 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
663 uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
664 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
665 uint64_t, PairU64.u, RTStrToUInt64Ex, 16, UINT64_MAX)
666 MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
667 uint64_t, PairU64.u, RTStrToUInt64Ex, 8, UINT64_MAX)
668
669 default:
670 AssertMsgFailed(("f=%#x\n", fFlags));
671 return VERR_INTERNAL_ERROR;
672 }
673
674 return VINF_SUCCESS;
675}
676
677
678/**
679 * Moves one argv option entries.
680 *
681 * @param papszTo Destination.
682 * @param papszFrom Source.
683 */
684static void rtGetOptMoveArgvEntries(char **papszTo, char **papszFrom)
685{
686 if (papszTo != papszFrom)
687 {
688 Assert((uintptr_t)papszTo < (uintptr_t)papszFrom);
689 char * const pszMoved = papszFrom[0];
690 memmove(&papszTo[1], &papszTo[0], (uintptr_t)papszFrom - (uintptr_t)papszTo);
691 papszTo[0] = pszMoved;
692 }
693}
694
695
696RTDECL(int) RTGetOpt(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion)
697{
698 /*
699 * Reset the variables kept in state.
700 */
701 pState->pDef = NULL;
702 pState->uIndex = UINT32_MAX;
703
704 /*
705 * Make sure the union is completely cleared out, whatever happens below.
706 */
707 pValueUnion->u64 = 0;
708 pValueUnion->pDef = NULL;
709
710 /*
711 * The next option.
712 */
713 bool fShort;
714 int iThis;
715 const char *pszArgThis;
716 size_t cchMatch;
717 PCRTGETOPTDEF pOpt;
718
719 if (pState->pszNextShort)
720 {
721 /*
722 * We've got short options left over from the previous call.
723 */
724 pOpt = rtGetOptSearchShort(*pState->pszNextShort, pState->paOptions, pState->cOptions, pState->fFlags);
725 if (!pOpt)
726 {
727 pValueUnion->psz = pState->pszNextShort;
728 return VERR_GETOPT_UNKNOWN_OPTION;
729 }
730 pszArgThis = pState->pszNextShort++;
731 cchMatch = 1;
732 iThis = pState->iNext;
733 fShort = true;
734 }
735 else
736 {
737 /*
738 * Pop off the next argument. Sorting options and dealing with the
739 * dash-dash makes this a little extra complicated.
740 */
741 for (;;)
742 {
743 if (pState->iNext >= pState->argc)
744 return 0;
745
746 if (pState->cNonOptions)
747 {
748 if (pState->cNonOptions == INT32_MAX)
749 {
750 pValueUnion->psz = pState->argv[pState->iNext++];
751 return VINF_GETOPT_NOT_OPTION;
752 }
753
754 if (pState->iNext + pState->cNonOptions >= pState->argc)
755 {
756 pState->cNonOptions = INT32_MAX;
757 continue;
758 }
759 }
760
761 iThis = pState->iNext++;
762 pszArgThis = pState->argv[iThis + pState->cNonOptions];
763
764 /*
765 * Do a long option search first and then a short option one.
766 * This way we can make sure single dash long options doesn't
767 * get mixed up with short ones.
768 */
769 pOpt = rtGetOptSearchLong(pszArgThis, pState->paOptions, pState->cOptions, pState->fFlags, &cchMatch);
770 fShort = false;
771 if (!pOpt)
772 {
773 const char *pszNext = pszArgThis;
774 RTUNICP uc;
775 int vrc = RTStrGetCpEx(&pszNext, &uc);
776 AssertRCReturn(vrc, vrc);
777 char chNext;
778 if ( rtGetOptIsDash(uc)
779 && (chNext = *pszNext) != '-'
780 && chNext != '\0'
781 && !((unsigned char)chNext & 0x80))
782 {
783 pOpt = rtGetOptSearchShort(chNext, pState->paOptions, pState->cOptions, pState->fFlags);
784 if (pOpt)
785 {
786 cchMatch = (size_t)(&pszNext[1] - pszArgThis);
787 fShort = true;
788 }
789 }
790 }
791
792 /* Look for dash-dash. */
793 if (!pOpt && rtGetOptLongStrEquals(pszArgThis, "--", RTSTR_MAX, 0, NULL))
794 {
795 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
796 pState->cNonOptions = INT32_MAX;
797 continue;
798 }
799
800 /* Options first hacks. */
801 if (pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST)
802 {
803 if (pOpt)
804 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
805 else if (rtGetOptIsDash(RTStrGetCp(pszArgThis)))
806 {
807 pValueUnion->psz = pszArgThis;
808 return VERR_GETOPT_UNKNOWN_OPTION;
809 }
810 else
811 {
812 /* not an option, add it to the non-options and try again. */
813 pState->iNext--;
814 pState->cNonOptions++;
815
816 /* Switch to returning non-options if we've reached the end. */
817 if (pState->iNext + pState->cNonOptions >= pState->argc)
818 pState->cNonOptions = INT32_MAX;
819 continue;
820 }
821 }
822
823 /* done */
824 break;
825 }
826 }
827
828 if (pOpt)
829 {
830 pValueUnion->pDef = pOpt; /* in case of no value or error. */
831
832 uint32_t const fOptFlags = pOpt->fFlags;
833 if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
834 {
835 /*
836 * Find the argument value.
837 *
838 * A value is required with the argument. We're trying to be
839 * understanding here and will permit any of the following:
840 * -svalue, -s value, -s:value and -s=value
841 * (Ditto for long options.)
842 */
843 const char *pszValue;
844 if (fShort)
845 {
846 if (pszArgThis[cchMatch] == '\0')
847 {
848 if (iThis + 1 >= pState->argc)
849 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
850 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
851 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
852 pState->iNext++;
853 }
854 else /* same argument. */
855 pszValue = &pszArgThis[cchMatch + (pszArgThis[cchMatch] == ':' || pszArgThis[cchMatch] == '=')];
856 if (pState->pszNextShort)
857 {
858 pState->pszNextShort = NULL;
859 pState->iNext++;
860 }
861 }
862 else
863 {
864 if (fOptFlags & RTGETOPT_FLAG_INDEX)
865 {
866 if ( pszArgThis[cchMatch] != '\0'
867 || (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
868 {
869 const char *pszNext = &pszArgThis[cchMatch];
870 uint32_t uIndex;
871 int rc = RTStrToUInt32Ex(pszNext, (char **)&pszNext, 10, &uIndex);
872 if ( rc == VERR_NO_DIGITS
873 && (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
874 {
875 uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
876 rc = pszNext[0] == '\0' ? VINF_SUCCESS : VWRN_TRAILING_CHARS;
877 }
878 if (rc == VWRN_TRAILING_CHARS)
879 {
880 if ( pszNext[0] != ':'
881 && pszNext[0] != '=')
882 return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
883 pState->uIndex = uIndex;
884 pszValue = pszNext + 1;
885 }
886 else if (rc == VINF_SUCCESS)
887 {
888 if (iThis + 1 + pState->cNonOptions >= pState->argc)
889 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
890 pState->uIndex = uIndex;
891 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
892 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
893 pState->iNext++;
894 }
895 else
896 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
897 }
898 else
899 return VERR_GETOPT_INDEX_MISSING;
900 }
901 else
902 {
903 if (pszArgThis[cchMatch] == '\0')
904 {
905 if (iThis + 1 + pState->cNonOptions >= pState->argc)
906 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
907 pszValue = pState->argv[iThis + pState->cNonOptions + 1];
908 rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
909 pState->iNext++;
910 }
911 else /* same argument. */
912 pszValue = &pszArgThis[cchMatch + 1];
913 }
914 }
915
916 /*
917 * Set up the ValueUnion.
918 */
919 int rc = rtGetOptProcessValue(fOptFlags, pszValue, pValueUnion);
920 if (RT_FAILURE(rc))
921 return rc;
922 }
923 else if (fShort)
924 {
925 /*
926 * Deal with "compressed" short option lists, correcting the next
927 * state variables for the start and end cases.
928 */
929 if (pszArgThis[cchMatch])
930 {
931 if (!pState->pszNextShort)
932 {
933 /* start */
934 pState->pszNextShort = &pszArgThis[cchMatch];
935 pState->iNext--;
936 }
937 }
938 else if (pState->pszNextShort)
939 {
940 /* end */
941 pState->pszNextShort = NULL;
942 pState->iNext++;
943 }
944 }
945 else if (fOptFlags & RTGETOPT_FLAG_INDEX)
946 {
947 uint32_t uIndex;
948 if (pszArgThis[cchMatch] != '\0')
949 {
950 if (RTStrToUInt32Full(&pszArgThis[cchMatch], 10, &uIndex) == VINF_SUCCESS)
951 pState->uIndex = uIndex;
952 else
953 AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
954 }
955 else if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK)
956 uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
957 else
958 return VERR_GETOPT_INDEX_MISSING;
959 }
960
961 pState->pDef = pOpt;
962 return pOpt->iShort;
963 }
964
965 /*
966 * Not a known option argument. If it starts with a switch char (-) we'll
967 * fail with unknown option, and if it doesn't we'll return it as a non-option.
968 */
969 if (*pszArgThis == '-')
970 {
971 pValueUnion->psz = pszArgThis;
972 return VERR_GETOPT_UNKNOWN_OPTION;
973 }
974
975 pValueUnion->psz = pszArgThis;
976 return VINF_GETOPT_NOT_OPTION;
977}
978RT_EXPORT_SYMBOL(RTGetOpt);
979
980
981RTDECL(int) RTGetOptFetchValue(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion, uint32_t fFlags)
982{
983 /*
984 * Validate input.
985 */
986 PCRTGETOPTDEF pOpt = pState->pDef;
987 AssertReturn(!(fFlags & ~RTGETOPT_VALID_MASK), VERR_INVALID_PARAMETER);
988 AssertReturn((fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING, VERR_INVALID_PARAMETER);
989
990 /*
991 * Make sure the union is completely cleared out, whatever happens below.
992 */
993 pValueUnion->u64 = 0;
994 pValueUnion->pDef = NULL;
995
996 /*
997 * Pop off the next argument and convert it into a value union.
998 */
999 if (pState->iNext >= pState->argc)
1000 return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
1001 int iThis = pState->iNext++;
1002 const char *pszValue = pState->argv[iThis + (pState->cNonOptions != INT32_MAX ? pState->cNonOptions : 0)];
1003 pValueUnion->pDef = pOpt; /* in case of no value or error. */
1004
1005 if (pState->cNonOptions && pState->cNonOptions != INT32_MAX)
1006 rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
1007
1008 return rtGetOptProcessValue(fFlags, pszValue, pValueUnion);
1009}
1010RT_EXPORT_SYMBOL(RTGetOptFetchValue);
1011
1012
1013RTDECL(char **) RTGetOptNonOptionArrayPtr(PRTGETOPTSTATE pState)
1014{
1015 AssertReturn(pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST, NULL);
1016 return &pState->argv[pState->iNext - 1];
1017}
1018RT_EXPORT_SYMBOL(RTGetOptNonOptionArrayPtr);
1019
1020
1021RTDECL(RTEXITCODE) RTGetOptPrintError(int ch, PCRTGETOPTUNION pValueUnion)
1022{
1023 if (ch == VINF_GETOPT_NOT_OPTION)
1024 RTMsgError("Invalid parameter: %s", pValueUnion->psz);
1025 else if (ch > 0)
1026 {
1027 if (RT_C_IS_GRAPH(ch))
1028 RTMsgError("Unhandled option: -%c", ch);
1029 else
1030 RTMsgError("Unhandled option: %i (%#x)", ch, ch);
1031 }
1032 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1033 RTMsgError("Unknown option: '%s'", pValueUnion->psz);
1034 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
1035 /** @todo r=klaus not really ideal, as the value isn't available */
1036 RTMsgError("The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
1037 else if (pValueUnion->pDef)
1038 RTMsgError("%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
1039 else
1040 RTMsgError("%Rrs\n", ch);
1041
1042 return RTEXITCODE_SYNTAX;
1043}
1044RT_EXPORT_SYMBOL(RTGetOptPrintError);
1045
1046
1047RTDECL(ssize_t) RTGetOptFormatError(char *pszBuf, size_t cbBuf, int ch, PCRTGETOPTUNION pValueUnion)
1048{
1049 ssize_t cchRet;
1050 if (ch == VINF_GETOPT_NOT_OPTION)
1051 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Invalid parameter: %s", pValueUnion->psz);
1052 else if (ch > 0)
1053 {
1054 if (RT_C_IS_GRAPH(ch))
1055 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: -%c", ch);
1056 else
1057 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: %i (%#x)", ch, ch);
1058 }
1059 else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1060 cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unknown option: '%s'", pValueUnion->psz);
1061 else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
1062 /** @todo r=klaus not really ideal, as the value isn't available */
1063 cchRet = RTStrPrintf2(pszBuf, cbBuf, "The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
1064 else if (pValueUnion->pDef)
1065 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
1066 else
1067 cchRet = RTStrPrintf2(pszBuf, cbBuf, "%Rrs\n", ch);
1068
1069 return cchRet;
1070}
1071RT_EXPORT_SYMBOL(RTGetOptFormatError);
1072
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