VirtualBox

Changeset 92671 in vbox for trunk/src/VBox


Ignore:
Timestamp:
Dec 1, 2021 12:35:07 PM (3 years ago)
Author:
vboxsync
Message:

IPRT/RTProcCreateEx/posix: Added RTPROC_FLAGS_UTF8_ARGV and implemented argument conversion when that isn't set. Also added missing path conversion of pszExec. bugref:10153

Location:
trunk/src/VBox/Runtime
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Runtime/include/internal/string.h

    r90821 r92671  
    9797                             char **ppszOutput, size_t cbOutput, const char *pszOutputCS,
    9898                             unsigned cFactor, RTSTRICONV enmCacheIdx);
     99DECLHIDDEN(void) rtStrLocalCacheInit(void **ppvTmpCache);
     100DECLHIDDEN(int)  rtStrLocalCacheConvert(const char *pchInput, size_t cchInput, const char *pszInputCS,
     101                                        char **ppszOutput, size_t cbOutput, const char *pszOutputCS,
     102                                        void **ppvTmpCache);
     103DECLHIDDEN(void) rtStrLocalCacheDelete(void **ppvTmpCache);
    99104DECLHIDDEN(const char *) rtStrGetLocaleCodeset(void);
     105DECLHIDDEN(bool)         rtStrIsLocaleCodesetUtf8(void);
     106DECLHIDDEN(bool)         rtStrIsCodesetUtf8(const char *pszCodeset);
    100107DECLHIDDEN(int) rtUtf8Length(const char *psz, size_t cch, size_t *pcuc, size_t *pcchActual);
    101108
  • trunk/src/VBox/Runtime/r3/posix/process-creation-posix.cpp

    r92657 r92671  
    3737# define _GNU_SOURCE
    3838#endif
     39#if defined(RT_OS_LINUX) && !defined(_XOPEN_SOURCE)
     40# define _XOPEN_SOURCE 700 /* for newlocale */
     41#endif
    3942
    4043#ifdef RT_OS_OS2
     
    5154#include <stdlib.h>
    5255#include <errno.h>
     56#include <langinfo.h>
     57#include <locale.h>
    5358#include <sys/types.h>
    5459#include <sys/stat.h>
     
    119124#include "internal/iprt.h"
    120125
     126#include <iprt/alloca.h>
    121127#include <iprt/assert.h>
    122128#include <iprt/ctype.h>
     
    134140#include <iprt/mem.h>
    135141#include "internal/process.h"
     142#include "internal/path.h"
     143#include "internal/string.h"
    136144
    137145
     
    12881296
    12891297/**
     1298 * Converts the arguments to the child's LC_CTYPE charset if necessary.
     1299 *
     1300 * @returns IPRT status code.
     1301 * @param   papszArgs   The arguments (UTF-8).
     1302 * @param   hEnvToUse   The child process environment.
     1303 * @param   ppapszArgs  Where to return the converted arguments.  The array
     1304 *                      entries must be freed by RTStrFree and the array itself
     1305 *                      by RTMemFree.
     1306 */
     1307static int rtProcPosixConvertArgv(const char * const *papszArgs, RTENV hEnvToUse, char ***ppapszArgs)
     1308{
     1309    *ppapszArgs = (char **)papszArgs;
     1310
     1311    /*
     1312     * The first thing we need to do here is to try guess the codeset of the
     1313     * child process and check if it's UTF-8 or not.
     1314     */
     1315    const char *pszEncoding;
     1316    char        szEncoding[512];
     1317    if (hEnvToUse == RTENV_DEFAULT)
     1318    {
     1319        /* Same environment as us, assume setlocale is up to date: */
     1320        pszEncoding = rtStrGetLocaleCodeset();
     1321    }
     1322    else
     1323    {
     1324        /* LC_ALL overrides everything else.*/
     1325        /** @todo I don't recall now if this can do LC_XXX= inside it's value, like
     1326         *        what setlocale returns on some systems.  It's been 15-16 years
     1327         *        since I last worked on an setlocale implementation... */
     1328        const char *pszVar;
     1329        int rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_ALL", szEncoding, sizeof(szEncoding), NULL);
     1330        if (rc == VERR_ENV_VAR_NOT_FOUND)
     1331            rc = RTEnvGetEx(hEnvToUse, pszVar = "LC_CTYPE", szEncoding, sizeof(szEncoding), NULL);
     1332        if (rc == VERR_ENV_VAR_NOT_FOUND)
     1333            rc = RTEnvGetEx(hEnvToUse, pszVar = "LANG", szEncoding, sizeof(szEncoding), NULL);
     1334        if (RT_SUCCESS(rc))
     1335        {
     1336            const char *pszDot = strchr(szEncoding, '.');
     1337            if (pszDot)
     1338                pszDot = RTStrStripL(pszDot + 1);
     1339            if (pszDot && *pszDot)
     1340            {
     1341                pszEncoding = pszDot;
     1342                Log2Func(("%s=%s -> %s (simple)\n", pszVar, szEncoding, pszEncoding));
     1343            }
     1344            else
     1345            {
     1346                 /* No charset is given, so the default of the locale should be
     1347                    used.  To get at that we have to use newlocale and nl_langinfo_l,
     1348                    which is there since ancient days on linux but no necessarily else
     1349                    where. */
     1350#ifdef LC_CTYPE_MASK
     1351                locale_t hLocale = newlocale(LC_CTYPE_MASK, szEncoding, (locale_t)0);
     1352                if (hLocale != (locale_t)0)
     1353                {
     1354                    const char *pszCodeset = nl_langinfo_l(CODESET, hLocale);
     1355                    Log2Func(("nl_langinfo_l(CODESET, %s=%s) -> %s\n", pszVar, szEncoding, pszCodeset));
     1356                    Assert(pszCodeset && *pszCodeset != '\0');
     1357
     1358                    rc = RTStrCopy(szEncoding, sizeof(szEncoding), pszCodeset);
     1359                    AssertRC(rc); /* cannot possibly overflow */
     1360
     1361                    freelocale(hLocale);
     1362                    pszEncoding = szEncoding;
     1363                }
     1364                else
     1365#endif
     1366                {
     1367                    /* This is mostly wrong, but I cannot think of anything better now: */
     1368                    pszEncoding = rtStrGetLocaleCodeset();
     1369                    LogFunc(("No newlocale or it failed (on '%s=%s', errno=%d), falling back on %s that we're using...\n",
     1370                             pszVar, szEncoding, errno, pszEncoding));
     1371                }
     1372            }
     1373            RT_NOREF_PV(pszVar);
     1374        }
     1375        else
     1376            pszEncoding = "C";
     1377    }
     1378
     1379    /*
     1380     * Do nothing if it's UTF-8.
     1381     */
     1382    if (rtStrIsCodesetUtf8(pszEncoding))
     1383    {
     1384        LogFlowFunc(("No conversion needed (%s)\n", pszEncoding));
     1385        return VINF_SUCCESS;
     1386    }
     1387
     1388
     1389    /*
     1390     * Do the conversion.
     1391     */
     1392    size_t cArgs = 0;
     1393    while (papszArgs[cArgs] != NULL)
     1394        cArgs++;
     1395    LogFunc(("Converting #%u arguments to %s...\n", cArgs, pszEncoding));
     1396
     1397    char **papszArgsConverted = (char **)RTMemAllocZ(sizeof(papszArgsConverted[0]) * (cArgs + 2));
     1398    AssertReturn(papszArgsConverted, VERR_NO_MEMORY);
     1399
     1400    void *pvConversionCache = NULL;
     1401    rtStrLocalCacheInit(&pvConversionCache);
     1402    for (size_t i = 0; i < cArgs; i++)
     1403    {
     1404        int rc = rtStrLocalCacheConvert(papszArgs[i], strlen(papszArgs[i]), "UTF-8",
     1405                                        &papszArgsConverted[i], 0, pszEncoding, &pvConversionCache);
     1406        if (RT_SUCCESS(rc) && rc != VWRN_NO_TRANSLATION)
     1407        { /* likely */ }
     1408        else
     1409        {
     1410            Log(("Failed to convert argument #%u '%s' to '%s': %Rrc\n", i, papszArgs[i], pszEncoding, rc));
     1411            while (i-- > 0)
     1412                RTStrFree(papszArgsConverted[i]);
     1413            RTMemFree(papszArgsConverted);
     1414            rtStrLocalCacheDelete(&pvConversionCache);
     1415            return rc == VWRN_NO_TRANSLATION ? VERR_NO_TRANSLATION : rc;
     1416        }
     1417    }
     1418
     1419    rtStrLocalCacheDelete(&pvConversionCache);
     1420    *ppapszArgs = papszArgsConverted;
     1421    return VINF_SUCCESS;
     1422}
     1423
     1424
     1425/**
     1426 * The result structure for rtPathFindExec/RTPathTraverseList.
     1427 * @todo move to common path code?
     1428 */
     1429typedef struct RTPATHINTSEARCH
     1430{
     1431    /** For EACCES or EPERM errors that we continued on.
     1432     * @note Must be initialized to VINF_SUCCESS. */
     1433    int  rcSticky;
     1434    /** Buffer containing the filename. */
     1435    char szFound[RTPATH_MAX];
     1436} RTPATHINTSEARCH;
     1437/** Pointer to a rtPathFindExec/RTPathTraverseList result. */
     1438typedef RTPATHINTSEARCH *PRTPATHINTSEARCH;
     1439
     1440
     1441/**
    12901442 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
    12911443 */
    12921444static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    12931445{
    1294     const char *pszExec     = (const char *)pvUser1;
    1295     char       *pszRealExec = (char *)pvUser2;
    1296     int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
    1297     if (RT_FAILURE(rc))
    1298         return rc;
    1299     if (!access(pszRealExec, X_OK))
    1300         return VINF_SUCCESS;
    1301     if (   errno == EACCES
    1302         || errno == EPERM)
    1303         return RTErrConvertFromErrno(errno);
    1304     return VERR_TRY_AGAIN;
     1446    const char      *pszExec = (const char *)pvUser1;
     1447    PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)pvUser2;
     1448    int rc = RTPathJoinEx(pResult->szFound, sizeof(pResult->szFound), pchPath, cchPath, pszExec, RTSTR_MAX);
     1449    if (RT_SUCCESS(rc))
     1450    {
     1451        const char *pszNativeExec = NULL;
     1452        rc = rtPathToNative(&pszNativeExec, pResult->szFound, NULL);
     1453        if (RT_SUCCESS(rc))
     1454        {
     1455            if (!access(pszNativeExec, X_OK))
     1456                rc = VINF_SUCCESS;
     1457            else
     1458            {
     1459                if (   errno == EACCES
     1460                    || errno == EPERM)
     1461                    pResult->rcSticky = RTErrConvertFromErrno(errno);
     1462                rc = VERR_TRY_AGAIN;
     1463            }
     1464            rtPathFreeNative(pszNativeExec, pResult->szFound);
     1465        }
     1466        else
     1467            AssertRCStmt(rc, rc = VERR_TRY_AGAIN /* don't stop on this, whatever it is */);
     1468    }
     1469    return rc;
    13051470}
    13061471
     
    14561621
    14571622    /*
    1458      * Check for execute access to the file.
    1459      */
    1460     char szRealExec[RTPATH_MAX];
    1461     if (access(pszExec, X_OK) == 0)
    1462         rc = VINF_SUCCESS;
    1463     else
    1464     {
    1465         rc = errno;
    1466         if (   !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
    1467             || rc != ENOENT
    1468             || RTPathHavePath(pszExec) )
    1469             rc = RTErrConvertFromErrno(rc);
     1623     * Check for execute access to the file, searching the PATH if needed.
     1624     */
     1625    const char *pszNativeExec = NULL;
     1626    rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
     1627    if (RT_SUCCESS(rc))
     1628    {
     1629        if (access(pszNativeExec, X_OK) == 0)
     1630            rc = VINF_SUCCESS;
    14701631        else
    14711632        {
    1472             /* search */
    1473             char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
    1474             rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
    1475             RTStrFree(pszPath);
     1633            rc = errno;
     1634            rtPathFreeNative(pszNativeExec, pszExec);
     1635
     1636            if (   !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
     1637                || rc != ENOENT
     1638                || RTPathHavePath(pszExec) )
     1639                rc = RTErrConvertFromErrno(rc);
     1640            else
     1641            {
     1642                /* Search the PATH for it: */
     1643                char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
     1644                if (pszPath)
     1645                {
     1646                    PRTPATHINTSEARCH pResult = (PRTPATHINTSEARCH)alloca(sizeof(*pResult));
     1647                    pResult->rcSticky = VINF_SUCCESS;
     1648                    rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, pResult);
     1649                    RTStrFree(pszPath);
     1650                    if (RT_SUCCESS(rc))
     1651                    {
     1652                        /* Found it. Now, convert to native path: */
     1653                        pszExec = pResult->szFound;
     1654                        rc = rtPathToNative(&pszNativeExec, pszExec, NULL);
     1655                    }
     1656                    else
     1657                        rc = rc != VERR_END_OF_STRING ? rc
     1658                           : pResult->rcSticky == VINF_SUCCESS ? VERR_FILE_NOT_FOUND : pResult->rcSticky;
     1659                }
     1660                else
     1661                    rc = VERR_NO_STR_MEMORY;
     1662            }
     1663        }
     1664        if (RT_SUCCESS(rc))
     1665        {
     1666            /*
     1667             * Convert arguments to child codeset if necessary.
     1668             */
     1669            char **papszArgsConverted = (char **)papszArgs;
     1670            if (!(fFlags & RTPROC_FLAGS_UTF8_ARGV))
     1671                rc = rtProcPosixConvertArgv(papszArgs, hEnvToUse, &papszArgsConverted);
    14761672            if (RT_SUCCESS(rc))
    1477                 pszExec = szRealExec;
    1478             else
    1479                 rc = rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
    1480         }
    1481     }
    1482     if (RT_SUCCESS(rc))
    1483     {
    1484         /*
    1485          * The rest of the process creation is reused internally by
    1486          * rtProcPosixCreateProfileEnv.
    1487          */
    1488         rc = rtProcPosixCreateInner(pszExec, papszArgs, hEnv, hEnvToUse, fFlags, pszAsUser, uid, gid,
    1489                                     RT_ELEMENTS(aStdFds), aStdFds, phProcess);
     1673            {
     1674                /*
     1675                 * The rest of the process creation is reused internally by rtProcPosixCreateProfileEnv.
     1676                 */
     1677                rc = rtProcPosixCreateInner(pszNativeExec, papszArgsConverted, hEnv, hEnvToUse, fFlags, pszAsUser, uid, gid,
     1678                                            RT_ELEMENTS(aStdFds), aStdFds, phProcess);
     1679
     1680            }
     1681
     1682            /* Free the translated argv copy, if needed. */
     1683            if (papszArgsConverted != (char **)papszArgs)
     1684            {
     1685                for (size_t i = 0; papszArgsConverted[i] != NULL; i++)
     1686                    RTStrFree(papszArgsConverted[i]);
     1687                RTMemFree(papszArgsConverted);
     1688            }
     1689            rtPathFreeNative(pszNativeExec, pszExec);
     1690        }
    14901691    }
    14911692    if (hEnvToUse != hEnv)
     
    15011702 *
    15021703 * @returns IPRT status code.
    1503  * @param   pszExec     The executable to run (absolute path, X_OK).
    1504  * @param   papszArgs   The arguments.
    1505  * @param   hEnv        The original enviornment request, needed for adjustments
    1506  *                      if starting as different user.
    1507  * @param   hEnvToUse   The environment we should use.
    1508  * @param   fFlags      The process creation flags, RTPROC_FLAGS_XXX.
    1509  * @param   pszAsUser   The user to start the process as, if requested.
    1510  * @param   uid         The UID corrsponding to @a pszAsUser, ~0 if NULL.
    1511  * @param   gid         The GID corrsponding to @a pszAsUser, ~0 if NULL.
    1512  * @param   cRedirFds   Number of redirection file descriptors.
    1513  * @param   paRedirFds  Pointer to redirection file descriptors.  Entries
    1514  *                      containing -1 are not modified (inherit from parent),
    1515  *                      -2 indicates that the descriptor should be closed in the
    1516  *                      child.
    1517  * @param   phProcess   Where to return the process ID on success.
     1704 * @param   pszNativeExec   The executable to run (absolute path, X_OK).
     1705 *                          Native path.
     1706 * @param   papszArgs       The arguments.  Caller has done codeset conversions.
     1707 * @param   hEnv            The original enviornment request, needed for
     1708 *                          adjustments if starting as different user.
     1709 * @param   hEnvToUse       The environment we should use.
     1710 * @param   fFlags          The process creation flags, RTPROC_FLAGS_XXX.
     1711 * @param   pszAsUser       The user to start the process as, if requested.
     1712 * @param   uid             The UID corrsponding to @a pszAsUser, ~0 if NULL.
     1713 * @param   gid             The GID corrsponding to @a pszAsUser, ~0 if NULL.
     1714 * @param   cRedirFds       Number of redirection file descriptors.
     1715 * @param   paRedirFds      Pointer to redirection file descriptors.  Entries
     1716 *                          containing -1 are not modified (inherit from parent),
     1717 *                          -2 indicates that the descriptor should be closed in the
     1718 *                          child.
     1719 * @param   phProcess       Where to return the process ID on success.
    15181720 */
    1519 static int rtProcPosixCreateInner(const char *pszExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse,
     1721static int rtProcPosixCreateInner(const char *pszNativeExec, const char * const *papszArgs, RTENV hEnv, RTENV hEnvToUse,
    15201722                                  uint32_t fFlags, const char *pszAsUser, uid_t uid, gid_t gid,
    15211723                                  unsigned cRedirFds, int *paRedirFds, PRTPROCESS phProcess)
     
    16791881
    16801882            if (!rc)
    1681                 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
     1883                rc = posix_spawn(&pid, pszNativeExec, pFileActions, &Attr, (char * const *)papszArgs,
    16821884                                 (char * const *)papszEnv);
    16831885
     
    18262028             * Finally, execute the requested program.
    18272029             */
    1828             rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
     2030            rc = execve(pszNativeExec, (char * const *)papszArgs, (char * const *)papszEnv);
    18292031            if (errno == ENOEXEC)
    18302032            {
  • trunk/src/VBox/Runtime/r3/posix/utf8-posix.cpp

    r82968 r92671  
    3434#include <iprt/alloc.h>
    3535#include <iprt/assert.h>
     36#include <iprt/ctype.h>
    3637#include <iprt/err.h>
    3738#include <iprt/string.h>
     
    108109    return nl_langinfo(CODESET);
    109110}
     111
     112
     113/**
     114 * Checks if the codeset specified by current locale (LC_CTYPE) is UTF-8.
     115 *
     116 * @returns true if UTF-8, false if not.
     117 */
     118DECLHIDDEN(bool) rtStrIsLocaleCodesetUtf8(void)
     119{
     120    return rtStrIsCodesetUtf8(rtStrGetLocaleCodeset());
     121}
     122
     123
     124/**
     125 * Checks if @a pszCodeset specified UTF-8.
     126 *
     127 * @returns true if UTF-8, false if not.
     128 * @param   pszCodeset      Codeset to test.
     129 */
     130DECLHIDDEN(bool) rtStrIsCodesetUtf8(const char *pszCodeset)
     131{
     132    if (pszCodeset)
     133    {
     134        /* Skip leading spaces just in case: */
     135        while (RT_C_IS_SPACE(*pszCodeset))
     136            pszCodeset++;
     137
     138        /* If prefixed by 'ISO-10646/' skip that (iconv access this, dunno about
     139           LC_CTYPE et al., but play it safe): */
     140        if (   strncmp(pszCodeset, RT_STR_TUPLE("ISO-10646/")) == 0
     141            || strncmp(pszCodeset, RT_STR_TUPLE("iso-10646/")) == 0)
     142            pszCodeset += sizeof("ISO-10646/") - 1;
     143
     144        /* Match 'utf': */
     145        if (   (pszCodeset[0] == 'u' || pszCodeset[0] == 'U')
     146            && (pszCodeset[1] == 't' || pszCodeset[1] == 'T')
     147            && (pszCodeset[2] == 'f' || pszCodeset[2] == 'F'))
     148        {
     149            pszCodeset += 3;
     150
     151            /* Treat the dash as optional: */
     152            if (*pszCodeset == '-')
     153                pszCodeset++;
     154
     155            /* Match '8': */
     156            if (*pszCodeset == '8')
     157            {
     158                do
     159                    pszCodeset++;
     160                while (RT_C_IS_SPACE(*pszCodeset));
     161
     162                /* We ignore modifiers here (e.g. "[be_BY.]utf8@latin"). */
     163                if (!*pszCodeset || *pszCodeset == '@')
     164                    return true;
     165            }
     166        }
     167    }
     168    return false;
     169}
     170
    110171
    111172
     
    475536
    476537
     538/**
     539 * Initializes a local conversion cache for use with rtStrLocalCacheConvert.
     540 *
     541 * Call rtStrLocalCacheDelete when done.
     542 */
     543DECLHIDDEN(void) rtStrLocalCacheInit(void **ppvTmpCache)
     544{
     545    *ppvTmpCache = (iconv_t)-1;
     546}
     547
     548
     549/**
     550 * Cleans up a local conversion cache.
     551 */
     552DECLHIDDEN(void) rtStrLocalCacheDelete(void **ppvTmpCache)
     553{
     554#ifdef RT_WITH_ICONV_CACHE
     555    iconv_t icHandle = (iconv_t)*ppvTmpCache;
     556    if (icHandle != (iconv_t)-1)
     557        iconv_close(icHandle);
     558#endif
     559    *ppvTmpCache = (iconv_t)-1;
     560}
     561
     562
     563/**
     564 * Internal API for use by the process creation conversion code.
     565 *
     566 * @returns IPRT status code.
     567 *
     568 * @param   pszInput        Pointer to intput string.
     569 * @param   cchInput        Size (in bytes) of input string. Excludes any
     570 *                          terminators.
     571 * @param   pszInputCS      Codeset of the input string.
     572 * @param   ppszOutput      Pointer to pointer to output buffer if cbOutput > 0.
     573 *                          If cbOutput is 0 this is where the pointer to the
     574 *                          allocated buffer is stored.
     575 * @param   cbOutput        Size of the passed in buffer.
     576 * @param   pszOutputCS     Codeset of the input string.
     577 * @param   ppvTmpCache     Pointer to local temporary cache.  Must be
     578 *                          initialized by calling rtStrLocalCacheInit and
     579 *                          cleaned up afterwards by rtStrLocalCacheDelete.
     580 *                          Optional.
     581 */
     582DECLHIDDEN(int) rtStrLocalCacheConvert(const char *pchInput, size_t cchInput, const char *pszInputCS,
     583                                       char **ppszOutput, size_t cbOutput, const char *pszOutputCS,
     584                                       void **ppvTmpCache)
     585{
     586#ifdef RT_WITH_ICONV_CACHE
     587    if (ppvTmpCache)
     588        return rtstrConvertCached(pchInput, cchInput, pszInputCS, (void **)ppszOutput, cbOutput, pszOutputCS,
     589                                  1 /*cFactor*/, (iconv_t *)ppvTmpCache);
     590#else
     591    RT_NOREF(ppvTmpCache);
     592#endif
     593
     594    return rtStrConvertUncached(pchInput, cchInput, pszInputCS, (void **)ppszOutput, cbOutput, pszOutputCS, 1 /*cFactor*/);
     595}
     596
     597
    477598RTR3DECL(int)  RTStrUtf8ToCurrentCPTag(char **ppszString, const char *pszString, const char *pszTag)
    478599{
Note: See TracChangeset for help on using the changeset viewer.

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