diff options
Diffstat (limited to 'src/VBox/Runtime/testcase/tstRTDigest.cpp')
-rw-r--r-- | src/VBox/Runtime/testcase/tstRTDigest.cpp | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/src/VBox/Runtime/testcase/tstRTDigest.cpp b/src/VBox/Runtime/testcase/tstRTDigest.cpp new file mode 100644 index 00000000..c2cfb096 --- /dev/null +++ b/src/VBox/Runtime/testcase/tstRTDigest.cpp @@ -0,0 +1,691 @@ +/* $Id: tstRTDigest.cpp $ */ +/** @file + * IPRT Testcase - RTSha*, RTMd5, RTCrc*. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/sha.h> +#include <iprt/md2.h> +#include <iprt/md5.h> +#include <iprt/crc.h> + +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/crypto/digest.h> + + +static int Error(const char *pszFormat, ...) +{ + char szName[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szName, sizeof(szName))) + strcpy(szName, "tstRTDigest"); + + RTStrmPrintf(g_pStdErr, "%s: error: ", RTPathFilename(szName)); + va_list va; + va_start(va, pszFormat); + RTStrmPrintfV(g_pStdErr, pszFormat, va); + va_end(va); + + return 1; +} + + +static int MyReadFile(RTFILE hFile, void *pvBuf, size_t cbToRead, size_t *pcbRead, uint64_t *pcbMaxLeft) +{ + int rc = VINF_SUCCESS; + if (*pcbMaxLeft > 0) + { + if (cbToRead > *pcbMaxLeft) + cbToRead = (size_t)*pcbMaxLeft; + rc = RTFileRead(hFile, pvBuf, cbToRead, pcbRead); + if (RT_SUCCESS(rc)) + *pcbMaxLeft -= *pcbRead; + } + else + *pcbRead = 0; + return rc; +} + + +static char *MyGetNextSignificantLine(PRTSTREAM pFile, char *pszBuf, size_t cbBuf, uint32_t *piLine, int *prc) +{ + for (;;) + { + *pszBuf = '\0'; + int rc = RTStrmGetLine(pFile, pszBuf, cbBuf); + if (RT_FAILURE(rc)) + { + if (rc != VERR_EOF) + { + Error("Read error: %Rrc", rc); + *prc = rc; + return NULL; + } + if (!*pszBuf) + return NULL; + } + *piLine += 1; + + /* Significant? */ + char *pszStart = RTStrStrip(pszBuf); + if (*pszStart && *pszStart != '#') + return pszStart; + } +} + + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + RTDIGESTTYPE enmDigestType = RTDIGESTTYPE_INVALID; + const char *pszDigestType = "NotSpecified"; + + enum + { + kMethod_Full, + kMethod_Block, + kMethod_File, + kMethod_CVAS + } enmMethod = kMethod_Block; + + uint64_t offStart = 0; + uint64_t cbMax = UINT64_MAX; + bool fTestcase = false; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--method", 'm', RTGETOPT_REQ_STRING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--length", 'l', RTGETOPT_REQ_UINT64 }, + { "--offset", 'o', RTGETOPT_REQ_UINT64 }, + { "--testcase", 'x', RTGETOPT_REQ_NOTHING }, + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 't': + if (!RTStrICmp(ValueUnion.psz, "crc32")) + { + pszDigestType = "CRC32"; + enmDigestType = RTDIGESTTYPE_CRC32; + } + else if (!RTStrICmp(ValueUnion.psz, "crc64")) + { + pszDigestType = "CRC64"; + enmDigestType = RTDIGESTTYPE_CRC64; + } + else if (!RTStrICmp(ValueUnion.psz, "md2")) + { + pszDigestType = "MD2"; + enmDigestType = RTDIGESTTYPE_MD2; + } + else if (!RTStrICmp(ValueUnion.psz, "md5")) + { + pszDigestType = "MD5"; + enmDigestType = RTDIGESTTYPE_MD5; + } + else if (!RTStrICmp(ValueUnion.psz, "sha1")) + { + pszDigestType = "SHA-1"; + enmDigestType = RTDIGESTTYPE_SHA1; + } + else if (!RTStrICmp(ValueUnion.psz, "sha224")) + { + pszDigestType = "SHA-224"; + enmDigestType = RTDIGESTTYPE_SHA224; + } + else if (!RTStrICmp(ValueUnion.psz, "sha256")) + { + pszDigestType = "SHA-256"; + enmDigestType = RTDIGESTTYPE_SHA256; + } + else if (!RTStrICmp(ValueUnion.psz, "sha384")) + { + pszDigestType = "SHA-384"; + enmDigestType = RTDIGESTTYPE_SHA384; + } + else if (!RTStrICmp(ValueUnion.psz, "sha512")) + { + pszDigestType = "SHA-512"; + enmDigestType = RTDIGESTTYPE_SHA512; + } + else if (!RTStrICmp(ValueUnion.psz, "sha512/224")) + { + pszDigestType = "SHA-512/224"; + enmDigestType = RTDIGESTTYPE_SHA512T224; + } + else if (!RTStrICmp(ValueUnion.psz, "sha512/256")) + { + pszDigestType = "SHA-512/256"; + enmDigestType = RTDIGESTTYPE_SHA3_256; + } + else if (!RTStrICmp(ValueUnion.psz, "sha3-224")) + { + pszDigestType = "SHA3-224"; + enmDigestType = RTDIGESTTYPE_SHA3_224; + } + else if (!RTStrICmp(ValueUnion.psz, "sha3-256")) + { + pszDigestType = "SHA3-256"; + enmDigestType = RTDIGESTTYPE_SHA3_256; + } + else if (!RTStrICmp(ValueUnion.psz, "sha3-384")) + { + pszDigestType = "SHA3-384"; + enmDigestType = RTDIGESTTYPE_SHA3_384; + } + else if (!RTStrICmp(ValueUnion.psz, "sha3-512")) + { + pszDigestType = "SHA3-512"; + enmDigestType = RTDIGESTTYPE_SHA3_512; + } +#if 0 + else if (!RTStrICmp(ValueUnion.psz, "shake128")) + { + pszDigestType = "SHAKE128"; + enmDigestType = RTDIGESTTYPE_SHAKE128; + } + else if (!RTStrICmp(ValueUnion.psz, "shake256")) + { + pszDigestType = "SHAKE256"; + enmDigestType = RTDIGESTTYPE_SHAKE256; + } +#endif + else + { + Error("Invalid digest type: %s\n", ValueUnion.psz); + return 1; + } + break; + + case 'm': + if (!RTStrICmp(ValueUnion.psz, "full")) + enmMethod = kMethod_Full; + else if (!RTStrICmp(ValueUnion.psz, "block")) + enmMethod = kMethod_Block; + else if (!RTStrICmp(ValueUnion.psz, "file")) + enmMethod = kMethod_File; + else if (!RTStrICmp(ValueUnion.psz, "cvas")) + enmMethod = kMethod_CVAS; + else + { + Error("Invalid digest method: %s\n", ValueUnion.psz); + return 1; + } + break; + + case 'l': + cbMax = ValueUnion.u64; + break; + + case 'o': + offStart = ValueUnion.u64; + break; + + case 'x': + fTestcase = true; + break; + + case 'h': + RTPrintf("usage: tstRTDigest -t <digest-type> [-o <offset>] [-l <length>] [-m method] [-x] file [file2 [..]]\n" + "\n" + "Options:\n" + " -t,--type <hash-algo>\n" + " -o,--offset <file-offset>\n" + " -l,--length <byte-count>\n" + " -m,--method <full|block|file|cvas>\n" + " block: Init+Update+Finalize, data from file(s). Default.\n" + " file: RTSha*DigestFromFile. Only SHA1 and SHA256.\n" + " cvas: NIST test vectors processed by RTCrDigest*.\n" + " full: Not implemented\n" + " -x,--testcase\n" + " For generating C code.\n" + ); + return 1; + + case VINF_GETOPT_NOT_OPTION: + { + if (enmDigestType == RTDIGESTTYPE_INVALID) + return Error("No digest type was specified\n"); + + switch (enmMethod) + { + case kMethod_Full: + return Error("Full file method is not implemented\n"); + + case kMethod_File: + if (offStart != 0 || cbMax != UINT64_MAX) + return Error("The -l and -o options do not work with the 'file' method."); + switch (enmDigestType) + { + case RTDIGESTTYPE_SHA1: + { + char *pszDigest; + int rc = RTSha1DigestFromFile(ValueUnion.psz, &pszDigest, NULL, NULL); + if (RT_FAILURE(rc)) + return Error("RTSha1Digest(%s,) -> %Rrc\n", ValueUnion.psz, rc); + RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); + RTStrFree(pszDigest); + break; + } + + case RTDIGESTTYPE_SHA256: + { + char *pszDigest; + int rc = RTSha256DigestFromFile(ValueUnion.psz, &pszDigest, NULL, NULL); + if (RT_FAILURE(rc)) + return Error("RTSha256Digest(%s,) -> %Rrc\n", ValueUnion.psz, rc); + RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); + RTStrFree(pszDigest); + break; + } + default: + return Error("The file method isn't implemented for this digest\n"); + } + break; + + case kMethod_Block: + { + RTFILE hFile; + int rc = RTFileOpen(&hFile, ValueUnion.psz, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(rc)) + return Error("RTFileOpen(,%s,) -> %Rrc\n", ValueUnion.psz, rc); + if (offStart != 0) + { + rc = RTFileSeek(hFile, offStart, RTFILE_SEEK_BEGIN, NULL); + if (RT_FAILURE(rc)) + return Error("RTFileSeek(%s,%ull) -> %Rrc\n", ValueUnion.psz, offStart, rc); + } + + uint64_t cbMaxLeft = cbMax; + size_t cbRead; + uint8_t abBuf[_64K]; + char *pszDigest = (char *)&abBuf[0]; + switch (enmDigestType) + { + case RTDIGESTTYPE_CRC32: + { + uint32_t uCRC32 = RTCrc32Start(); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + uCRC32 = RTCrc32Process(uCRC32, abBuf, cbRead); + } + uCRC32 = RTCrc32Finish(uCRC32); + RTStrPrintf(pszDigest, sizeof(abBuf), "%08RX32", uCRC32); + break; + } + + case RTDIGESTTYPE_CRC64: + { + uint64_t uCRC64 = RTCrc64Start(); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + uCRC64 = RTCrc64Process(uCRC64, abBuf, cbRead); + } + uCRC64 = RTCrc64Finish(uCRC64); + RTStrPrintf(pszDigest, sizeof(abBuf), "%016RX64", uCRC64); + break; + } + + case RTDIGESTTYPE_MD2: + { + RTMD2CONTEXT Ctx; + RTMd2Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTMd2Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTMD2_HASH_SIZE]; + RTMd2Final(&Ctx, abDigest); + RTMd2ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_MD5: + { + RTMD5CONTEXT Ctx; + RTMd5Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTMd5Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTMD5HASHSIZE]; + RTMd5Final(abDigest, &Ctx); + RTMd5ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA1: + { + RTSHA1CONTEXT Ctx; + RTSha1Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha1Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA1_HASH_SIZE]; + RTSha1Final(&Ctx, abDigest); + RTSha1ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA256: + { + RTSHA256CONTEXT Ctx; + RTSha256Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha256Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA256_HASH_SIZE]; + RTSha256Final(&Ctx, abDigest); + RTSha256ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA512: + { + RTSHA512CONTEXT Ctx; + RTSha512Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha512Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA512_HASH_SIZE]; + RTSha512Final(&Ctx, abDigest); + RTSha512ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA3_224: + { + RTSHA3T224CONTEXT Ctx; + RTSha3t224Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha3t224Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA3_224_HASH_SIZE]; + RTSha3t224Final(&Ctx, abDigest); + RTSha3t224ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA3_256: + { + RTSHA3T256CONTEXT Ctx; + RTSha3t256Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha3t256Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA3_256_HASH_SIZE]; + RTSha3t256Final(&Ctx, abDigest); + RTSha3t256ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA3_384: + { + RTSHA3T384CONTEXT Ctx; + RTSha3t384Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha3t384Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA3_384_HASH_SIZE]; + RTSha3t384Final(&Ctx, abDigest); + RTSha3t384ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + case RTDIGESTTYPE_SHA3_512: + { + RTSHA3T512CONTEXT Ctx; + RTSha3t512Init(&Ctx); + for (;;) + { + rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); + if (RT_FAILURE(rc) || !cbRead) + break; + RTSha3t512Update(&Ctx, abBuf, cbRead); + } + uint8_t abDigest[RTSHA3_512_HASH_SIZE]; + RTSha3t512Final(&Ctx, abDigest); + RTSha3t512ToString(abDigest, pszDigest, sizeof(abBuf)); + break; + } + + /** @todo SHAKE128 and SHAKE256 */ + + default: + return Error("Internal error #1: %d %s\n", enmDigestType, pszDigest); + } + RTFileClose(hFile); + if (RT_FAILURE(rc) && rc != VERR_EOF) + { + RTPrintf("Partial: %s %s\n", pszDigest, ValueUnion.psz); + return Error("RTFileRead(%s) -> %Rrc\n", ValueUnion.psz, rc); + } + + if (!fTestcase) + RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); + else if (offStart) + RTPrintf(" { &g_abRandom72KB[%#4llx], %5llu, \"%s\", \"%s %llu bytes @%llu\" },\n", + offStart, cbMax - cbMaxLeft, pszDigest, pszDigestType, offStart, cbMax - cbMaxLeft); + else + RTPrintf(" { &g_abRandom72KB[0], %5llu, \"%s\", \"%s %llu bytes\" },\n", + cbMax - cbMaxLeft, pszDigest, pszDigestType, cbMax - cbMaxLeft); + break; + } + + + /* + * Process a SHS response file: + * http://csrc.nist.gov/groups/STM/cavp/index.html#03 + */ + case kMethod_CVAS: + { + RTCRDIGEST hDigest; + int rc = RTCrDigestCreateByType(&hDigest, enmDigestType); + if (RT_FAILURE(rc)) + return Error("Failed to create digest calculator for %s: %Rrc", pszDigestType, rc); + + uint32_t const cbDigest = RTCrDigestGetHashSize(hDigest); + if (!cbDigest || cbDigest >= _1K) + return Error("Unexpected hash size: %#x\n", cbDigest); + + PRTSTREAM pFile; + rc = RTStrmOpen(ValueUnion.psz, "r", &pFile); + if (RT_FAILURE(rc)) + return Error("Failed to open CVAS file '%s': %Rrc\n", ValueUnion.psz, rc); + + /* + * Parse the input file. + * ASSUME order: Len, Msg, MD. + */ + static char s_szLine[_256K]; + char *psz; + uint32_t cPassed = 0; + uint32_t cErrors = 0; + uint32_t iLine = 1; + for (;;) + { + psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); + if (!psz) + break; + + /* Skip [L = 20] stuff. */ + if (*psz == '[') + continue; + + /* Message length. */ + uint64_t cMessageBits; + if (RTStrNICmp(psz, RT_STR_TUPLE("Len ="))) + return Error("%s(%d): Expected 'Len =' found '%.10s...'", ValueUnion.psz, iLine, psz); + psz = RTStrStripL(psz + 5); + rc = RTStrToUInt64Full(psz, 0, &cMessageBits); + if (rc != VINF_SUCCESS) + return Error("%s(%d): Error parsing length '%s': %Rrc\n", ValueUnion.psz, iLine, psz, rc); + + /* The message text. */ + psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); + if (!psz) + return Error("%s(%d): Expected message text not EOF.", ValueUnion.psz, iLine); + if (RTStrNICmp(psz, RT_STR_TUPLE("Msg ="))) + return Error("%s(%d): Expected 'Msg =' found '%.10s...'", ValueUnion.psz, iLine, psz); + psz = RTStrStripL(psz + 5); + + size_t const cbMessage = (cMessageBits + 7) / 8; + static uint8_t s_abMessage[sizeof(s_szLine) / 2]; + if (cbMessage > 0) + { + rc = RTStrConvertHexBytes(psz, s_abMessage, cbMessage, 0 /*fFlags*/); + if (rc != VINF_SUCCESS) + return Error("%s(%d): Error parsing message '%.10s...': %Rrc\n", + ValueUnion.psz, iLine, psz, rc); + } + + /* The message digest. */ + psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); + if (!psz) + return Error("%s(%d): Expected message digest not EOF.", ValueUnion.psz, iLine); + if (RTStrNICmp(psz, RT_STR_TUPLE("MD ="))) + return Error("%s(%d): Expected 'MD =' found '%.10s...'", ValueUnion.psz, iLine, psz); + psz = RTStrStripL(psz + 4); + + static uint8_t s_abExpectedDigest[_1K]; + rc = RTStrConvertHexBytes(psz, s_abExpectedDigest, cbDigest, 0 /*fFlags*/); + if (rc != VINF_SUCCESS) + return Error("%s(%d): Error parsing message digest '%.10s...': %Rrc\n", + ValueUnion.psz, iLine, psz, rc); + + /* + * Do the testing. + */ + rc = RTCrDigestReset(hDigest); + if (rc != VINF_SUCCESS) + return Error("RTCrDigestReset failed: %Rrc", rc); + + rc = RTCrDigestUpdate(hDigest, s_abMessage, cbMessage); + if (rc != VINF_SUCCESS) + return Error("RTCrDigestUpdate failed: %Rrc", rc); + + static uint8_t s_abActualDigest[_1K]; + rc = RTCrDigestFinal(hDigest, s_abActualDigest, cbDigest); + if (rc != VINF_SUCCESS) + return Error("RTCrDigestFinal failed: %Rrc", rc); + + if (memcmp(s_abActualDigest, s_abExpectedDigest, cbDigest) == 0) + cPassed++; + else + { + Error("%s(%d): Message digest mismatch. Expected %.*RThxs, got %.*RThxs.", + ValueUnion.psz, iLine, cbDigest, s_abExpectedDigest, cbDigest, s_abActualDigest); + cErrors++; + } + } + + RTStrmClose(pFile); + if (cErrors > 0) + return Error("Failed: %u error%s (%u passed)", cErrors, cErrors == 1 ? "" : "s", cPassed); + RTPrintf("Passed %u test%s.\n", cPassed, cPassed == 1 ? "" : "s"); + if (RT_FAILURE(rc)) + return Error("Failed: %Rrc", rc); + break; + } + + default: + return Error("Internal error #2\n"); + } + break; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + return 0; +} + |