VirtualBox

Ignore:
Timestamp:
Oct 17, 2024 7:44:43 AM (4 months ago)
Author:
vboxsync
Message:

Additions/VBoxTray: Implemented ability for easier user-controllable logging (also via verbose levels), support for running in foreground mode (with a console window attached to) and selective starting of sub services to easier pinpoint errors in release builds. Cleaned up initialization / termination code a little. See command line help for new options. bugref:10763

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxTray.cpp

    r106246 r106411  
    5353#include <iprt/asm.h>
    5454#include <iprt/buildconfig.h>
     55#include <iprt/file.h>
    5556#include <iprt/getopt.h>
    5657#include <iprt/ldr.h>
     
    5859#include <iprt/path.h>
    5960#include <iprt/process.h>
     61#include <iprt/stream.h>
    6062#include <iprt/system.h>
    6163#include <iprt/time.h>
     
    8082*   Global Variables                                                                                                             *
    8183*********************************************************************************************************************************/
    82 int                   g_cVerbosity             = 0;
     84/** Mutex for checking if VBoxTray already is running. */
     85HANDLE                g_hMutexAppRunning       = NULL;
     86/** Whether VBoxTray is connected to a (parent) console. */
     87bool                  g_fHasConsole            = false;
     88/** The current verbosity level. */
     89unsigned              g_cVerbosity             = 0;
    8390HANDLE                g_hStopSem;
    8491HANDLE                g_hSeamlessWtNotifyEvent = 0;
     
    9097uint32_t              g_fGuestDisplaysChanged = 0;
    9198
    92 static PRTLOGGER      g_pLoggerRelease = NULL;           /**< This is actually the debug logger in DEBUG builds! */
    93 static uint32_t       g_cHistory = 10;                   /**< Enable log rotation, 10 files. */
    94 static uint32_t       g_uHistoryFileTime = RT_SEC_1DAY;  /**< Max 1 day per file. */
    95 static uint64_t       g_uHistoryFileSize = 100 * _1M;    /**< Max 100MB per file. */
    96 
    97 #ifdef DEBUG_andy
    98 static VBOXSERVICEINFO g_aServices[] =
    99 {
    100     { &g_SvcDescClipboard,      NIL_RTTHREAD, NULL, false, false, false, false, true }
    101 };
    102 #else
     99
    103100/**
    104101 * The details of the services that has been compiled in.
    105102 */
    106 static VBOXSERVICEINFO g_aServices[] =
     103static VBOXTRAYSVCINFO g_aServices[] =
    107104{
    108105    { &g_SvcDescDisplay,        NIL_RTTHREAD, NULL, false, false, false, false, true },
     
    118115#endif
    119116};
    120 #endif
    121 
    122 /* The global message table. */
    123 static VBOXGLOBALMESSAGE g_vboxGlobalMessageTable[] =
     117
     118/**
     119 * The global message table.
     120 */
     121static VBOXTRAYGLOBALMSG g_vboxGlobalMessageTable[] =
    124122{
    125123    /* Windows specific stuff. */
     
    131129    /* VBoxTray specific stuff. */
    132130    /** @todo Add new messages here! */
    133 
    134131    {
    135132        NULL
     
    141138 * get (re-)created. Nice to install our tray icon.
    142139 *
    143  * @return  IPRT status code.
     140 * @return  VBox status code.
    144141 * @param   wParam
    145142 * @param   lParam
     
    151148}
    152149
     150/**
     151 * Creates VBoxTray's tray icon.
     152 *
     153 * @returns VBox status code.
     154 */
    153155static int vboxTrayCreateTrayIcon(void)
    154156{
     
    156158    if (hIcon == NULL)
    157159    {
    158         DWORD dwErr = GetLastError();
    159         LogFunc(("Could not load tray icon, error %08X\n", dwErr));
     160        DWORD const dwErr = GetLastError();
     161        VBoxTrayError("Could not load tray icon (%#x)\n", dwErr);
    160162        return RTErrConvertFromWin32(dwErr);
    161163    }
     
    176178    if (!Shell_NotifyIcon(NIM_ADD, &g_NotifyIconData))
    177179    {
    178         DWORD dwErr = GetLastError();
    179         LogFunc(("Could not create tray icon, error=%ld\n", dwErr));
     180        DWORD const dwErr = GetLastError();
     181        VBoxTrayError("Could not create tray icon (%#x)\n", dwErr);
    180182        rc = RTErrConvertFromWin32(dwErr);
    181183        RT_ZERO(g_NotifyIconData);
     
    187189}
    188190
     191/**
     192 * Removes VBoxTray's tray icon.
     193 *
     194 * @returns VBox status code.
     195 */
    189196static void vboxTrayRemoveTrayIcon(void)
    190197{
     
    213220static DECLCALLBACK(int) vboxTrayServiceThread(RTTHREAD ThreadSelf, void *pvUser)
    214221{
    215     PVBOXSERVICEINFO pSvc = (PVBOXSERVICEINFO)pvUser;
     222    PVBOXTRAYSVCINFO pSvc = (PVBOXTRAYSVCINFO)pvUser;
    216223    AssertPtr(pSvc);
    217224
     
    229236    RTThreadUserSignal(ThreadSelf);
    230237
    231     LogFunc(("Worker for '%s' ended with %Rrc\n", pSvc->pDesc->pszName, rc));
     238    VBoxTrayVerbose(1, "Thread for '%s' ended with %Rrc\n", pSvc->pDesc->pszName, rc);
    232239    return rc;
    233240}
    234241
    235 static int vboxTrayServicesStart(PVBOXSERVICEENV pEnv)
     242/**
     243 * Lazily calls the pfnPreInit method on each service.
     244 *
     245 * @returns VBox status code, error message displayed.
     246 */
     247static int vboxTrayServicesLazyPreInit(void)
     248{
     249    for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
     250        if (!g_aServices[j].fPreInited)
     251        {
     252            int rc = g_aServices[j].pDesc->pfnPreInit();
     253            if (RT_FAILURE(rc))
     254                return VBoxTrayError("Service '%s' failed pre-init: %Rrc\n", g_aServices[j].pDesc->pszName, rc);
     255            g_aServices[j].fPreInited = true;
     256        }
     257    return VINF_SUCCESS;
     258}
     259
     260/**
     261 * Starts all services.
     262 *
     263 * @returns VBox status code.
     264 * @param   pEnv                Service environment to use.
     265 */
     266static int vboxTrayServicesStart(PVBOXTRAYSVCENV pEnv)
    236267{
    237268    AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
    238269
    239     LogRel(("Starting services ...\n"));
     270    VBoxTrayInfo("Starting services ...\n");
    240271
    241272    int rc = VINF_SUCCESS;
    242273
     274    size_t cServicesStarted = 0;
     275
    243276    for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
    244277    {
    245         PVBOXSERVICEINFO pSvc = &g_aServices[i];
    246         LogRel(("Starting service '%s' ...\n", pSvc->pDesc->pszName));
     278        PVBOXTRAYSVCINFO pSvc = &g_aServices[i];
     279
     280        if (!pSvc->fEnabled)
     281        {
     282            VBoxTrayInfo("Skipping starting service '%s' (disabled)\n", pSvc->pDesc->pszName);
     283            continue;
     284        }
     285
     286        VBoxTrayInfo("Starting service '%s' ...\n", pSvc->pDesc->pszName);
    247287
    248288        pSvc->hThread   = NIL_RTTHREAD;
     
    261301            {
    262302                case VERR_NOT_SUPPORTED:
    263                     LogRel(("Service '%s' is not supported on this system\n", pSvc->pDesc->pszName));
     303                    VBoxTrayInfo("Service '%s' is not supported on this system\n", pSvc->pDesc->pszName);
    264304                    rc2 = VINF_SUCCESS; /* Keep going. */
    265305                    break;
    266306
    267307                case VERR_HGCM_SERVICE_NOT_FOUND:
    268                     LogRel(("Service '%s' is not available on the host\n", pSvc->pDesc->pszName));
     308                    VBoxTrayInfo("Service '%s' is not available on the host\n", pSvc->pDesc->pszName);
    269309                    rc2 = VINF_SUCCESS; /* Keep going. */
    270310                    break;
    271311
    272312                default:
    273                     LogRel(("Failed to initialize service '%s', rc=%Rrc\n", pSvc->pDesc->pszName, rc2));
     313                    VBoxTrayError("Failed to initialize service '%s', rc=%Rrc\n", pSvc->pDesc->pszName, rc2);
    274314                    break;
    275315            }
     
    288328                    if (pSvc->fShutdown)
    289329                    {
    290                         LogRel(("Service '%s' failed to start!\n", pSvc->pDesc->pszName));
     330                        VBoxTrayError("Service '%s' failed to start!\n", pSvc->pDesc->pszName);
    291331                        rc = VERR_GENERAL_FAILURE;
    292332                    }
    293333                    else
    294                         LogRel(("Service '%s' started\n", pSvc->pDesc->pszName));
     334                    {
     335                        cServicesStarted++;
     336                        VBoxTrayInfo("Service '%s' started\n", pSvc->pDesc->pszName);
     337                    }
    295338                }
    296339                else
    297340                {
    298                     LogRel(("Failed to start thread for service '%s': %Rrc\n", rc2));
     341                    VBoxTrayInfo("Failed to start thread for service '%s': %Rrc\n", rc2);
    299342                    if (pSvc->pDesc->pfnDestroy)
    300343                        pSvc->pDesc->pfnDestroy(pSvc->pInstance);
     
    307350    }
    308351
    309     if (RT_SUCCESS(rc))
    310         LogRel(("All services started\n"));
    311     else
    312         LogRel(("Services started, but some with errors\n"));
     352    VBoxTrayInfo("%zu/%zu service(s) started\n", cServicesStarted, RT_ELEMENTS(g_aServices));
     353    if (RT_FAILURE(rc))
     354        VBoxTrayInfo("Some service(s) reported errors when starting -- see log above\n");
    313355
    314356    LogFlowFuncLeaveRC(rc);
     
    316358}
    317359
    318 static int vboxTrayServicesStop(VBOXSERVICEENV *pEnv)
     360/**
     361 * Stops all services.
     362 *
     363 * @returns VBox status code.
     364 * @param   pEnv                Service environment to use.
     365 */
     366static int vboxTrayServicesStop(VBOXTRAYSVCENV *pEnv)
    319367{
    320368    AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
    321369
    322     LogRel2(("Stopping all services ...\n"));
     370    VBoxTrayVerbose(1, "Stopping all services ...\n");
    323371
    324372    /*
     
    333381    for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
    334382    {
    335         PVBOXSERVICEINFO pSvc = &g_aServices[i];
     383        PVBOXTRAYSVCINFO pSvc = &g_aServices[i];
    336384        if (   pSvc->fStarted
    337385            && pSvc->pDesc->pfnStop)
    338386        {
    339             LogRel2(("Calling stop function for service '%s' ...\n", pSvc->pDesc->pszName));
     387            VBoxTrayVerbose(1, "Calling stop function for service '%s' ...\n", pSvc->pDesc->pszName);
    340388            int rc2 = pSvc->pDesc->pfnStop(pSvc->pInstance);
    341389            if (RT_FAILURE(rc2))
    342                 LogRel(("Failed to stop service '%s': %Rrc\n", pSvc->pDesc->pszName, rc2));
    343         }
    344     }
    345 
    346     LogRel2(("All stop functions for services called\n"));
     390                VBoxTrayError("Failed to stop service '%s': %Rrc\n", pSvc->pDesc->pszName, rc2);
     391        }
     392    }
     393
     394    VBoxTrayVerbose(2, "All stop functions for services called\n");
    347395
    348396    int rc = VINF_SUCCESS;
     
    353401    for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
    354402    {
    355         PVBOXSERVICEINFO pSvc = &g_aServices[i];
     403        PVBOXTRAYSVCINFO pSvc = &g_aServices[i];
    356404        if (!pSvc->fEnabled) /* Only stop services which were started before. */
    357405            continue;
     
    359407        if (pSvc->hThread != NIL_RTTHREAD)
    360408        {
    361             LogRel2(("Waiting for service '%s' to stop ...\n", pSvc->pDesc->pszName));
     409            VBoxTrayVerbose(1, "Waiting for service '%s' to stop ...\n", pSvc->pDesc->pszName);
    362410            int rc2 = VINF_SUCCESS;
    363411            for (int j = 0; j < 30; j++) /* Wait 30 seconds in total */
     
    369417            if (RT_FAILURE(rc2))
    370418            {
    371                 LogRel(("Service '%s' failed to stop (%Rrc)\n", pSvc->pDesc->pszName, rc2));
     419                VBoxTrayError("Service '%s' failed to stop (%Rrc)\n", pSvc->pDesc->pszName, rc2);
    372420                if (RT_SUCCESS(rc))
    373421                    rc = rc2;
     
    378426            && pSvc->pInstance) /* pInstance might be NULL if initialization of a service failed. */
    379427        {
    380             LogRel2(("Terminating service '%s' ...\n", pSvc->pDesc->pszName));
     428            VBoxTrayVerbose(1, "Terminating service '%s' ...\n", pSvc->pDesc->pszName);
    381429            pSvc->pDesc->pfnDestroy(pSvc->pInstance);
    382430        }
     
    384432
    385433    if (RT_SUCCESS(rc))
    386         LogRel(("All services stopped\n"));
     434        VBoxTrayVerbose(1, "All services stopped\n");
    387435
    388436    LogFlowFuncLeaveRC(rc);
     
    390438}
    391439
    392 static int vboxTrayRegisterGlobalMessages(PVBOXGLOBALMESSAGE pTable)
     440/**
     441 * Registers all global window messages of a specific table.
     442 *
     443 * @returns VBox status code.
     444 * @param   pTable              Table to register messages for.
     445 */
     446static int vboxTrayRegisterGlobalMessages(PVBOXTRAYGLOBALMSG pTable)
    393447{
    394448    int rc = VINF_SUCCESS;
     
    403457        {
    404458            DWORD dwErr = GetLastError();
    405             Log(("Registering global message \"%s\" failed, error = %08X\n", dwErr));
     459            VBoxTrayError("Registering global message \"%s\" failed, error = %08X\n", pTable->pszName, dwErr);
    406460            rc = RTErrConvertFromWin32(dwErr);
    407461        }
     
    413467}
    414468
    415 static bool vboxTrayHandleGlobalMessages(PVBOXGLOBALMESSAGE pTable, UINT uMsg,
     469/**
     470 * Handler for global (registered) window messages.
     471 *
     472 * @returns \c true if message got handeled, \c false if not.
     473 * @param   pTable              Message table to look message up in.
     474 * @param   uMsg                Message ID to handle.
     475 * @param   wParam              WPARAM of the message.
     476 * @param   lParam              LPARAM of the message.
     477 */
     478static bool vboxTrayHandleGlobalMessages(PVBOXTRAYGLOBALMSG pTable, UINT uMsg,
    416479                                         WPARAM wParam, LPARAM lParam)
    417480{
     
    434497
    435498/**
    436  * Header/footer callback for the release logger.
    437  *
    438  * @param   pLoggerRelease
    439  * @param   enmPhase
    440  * @param   pfnLog
    441  */
    442 static DECLCALLBACK(void) vboxTrayLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
    443 {
    444     /* Some introductory information. */
    445     static RTTIMESPEC s_TimeSpec;
    446     char szTmp[256];
    447     if (enmPhase == RTLOGPHASE_BEGIN)
    448         RTTimeNow(&s_TimeSpec);
    449     RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
    450 
    451     switch (enmPhase)
    452     {
    453         case RTLOGPHASE_BEGIN:
    454         {
    455             pfnLog(pLoggerRelease,
    456                    "VBoxTray %s r%s %s (%s %s) release log\n"
    457                    "Log opened %s\n",
    458                    RTBldCfgVersion(), RTBldCfgRevisionStr(), VBOX_BUILD_TARGET,
    459                    __DATE__, __TIME__, szTmp);
    460 
    461             int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
    462             if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
    463                 pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
    464             vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
    465             if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
    466                 pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
    467             vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
    468             if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
    469                 pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
    470             if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
    471                 pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
    472 
    473             /* the package type is interesting for Linux distributions */
    474             char szExecName[RTPATH_MAX];
    475             char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
    476             pfnLog(pLoggerRelease,
    477                    "Executable: %s\n"
    478                    "Process ID: %u\n"
    479                    "Package type: %s"
    480 #ifdef VBOX_OSE
    481                    " (OSE)"
    482 #endif
    483                    "\n",
    484                    pszExecName ? pszExecName : "unknown",
    485                    RTProcSelf(),
    486                    VBOX_PACKAGE_STRING);
    487             break;
    488         }
    489 
    490         case RTLOGPHASE_PREROTATE:
    491             pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
    492             break;
    493 
    494         case RTLOGPHASE_POSTROTATE:
    495             pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
    496             break;
    497 
    498         case RTLOGPHASE_END:
    499             pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
    500             break;
    501 
    502         default:
    503             /* nothing */;
    504     }
    505 }
    506 
    507 /**
    508  * Creates the default release logger outputting to the specified file.
    509  *
    510  * @return  IPRT status code.
    511  * @param   pszLogFile          Path to log file to use. Can be NULL if not needed.
    512  */
    513 static int vboxTrayLogCreate(const char *pszLogFile)
    514 {
    515     /* Create release (or debug) logger (stdout + file). */
    516     static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
    517 #ifdef DEBUG
    518     static const char s_szEnvVarPfx[] = "VBOXTRAY_LOG";
    519     static const char s_szGroupSettings[] = "all.e.l.f";
    520 #else
    521     static const char s_szEnvVarPfx[] = "VBOXTRAY_RELEASE_LOG";
    522     static const char s_szGroupSettings[] = "all";
    523 #endif
    524     RTERRINFOSTATIC ErrInfo;
    525     int rc = RTLogCreateEx(&g_pLoggerRelease, s_szEnvVarPfx,
    526                            RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG | RTLOGFLAGS_USECRLF,
    527                            s_szGroupSettings, RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX,
    528                            0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT,
    529                            vboxTrayLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
    530                            NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
    531                            RTErrInfoInitStatic(&ErrInfo), "%s", pszLogFile ? pszLogFile : "");
    532     if (RT_SUCCESS(rc))
    533     {
    534 #ifdef DEBUG
    535         /* Register this logger as the _debug_ logger. */
    536         RTLogSetDefaultInstance(g_pLoggerRelease);
    537 #else
    538         /* Register this logger as the release logger. */
    539         RTLogRelSetDefaultInstance(g_pLoggerRelease);
    540 #endif
    541         /* If verbosity is explicitly set, make sure to increase the logging levels for
    542          * the logging groups we offer functionality for in VBoxTray. */
    543         if (g_cVerbosity)
    544         {
    545             /* All groups we want to enable logging for VBoxTray. */
    546 #ifdef DEBUG
    547             const char *apszGroups[] = { "guest_dnd", "shared_clipboard" };
    548 #else /* For release builds we always want all groups being logged in verbose mode. Don't change this! */
    549             const char *apszGroups[] = { "all" };
    550 #endif
    551             char        szGroupSettings[_1K];
    552 
    553             szGroupSettings[0] = '\0';
    554 
    555             for (size_t i = 0; i < RT_ELEMENTS(apszGroups); i++)
    556             {
    557                 if (i > 0)
    558                     rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), "+");
    559                 if (RT_SUCCESS(rc))
    560                     rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), apszGroups[i]);
    561                 if (RT_FAILURE(rc))
    562                     break;
    563 
    564                 switch (g_cVerbosity)
    565                 {
    566                     case 1:
    567                         rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), ".e.l.l2");
    568                         break;
    569 
    570                     case 2:
    571                         rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), ".e.l.l2.l3");
    572                         break;
    573 
    574                     case 3:
    575                         rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), ".e.l.l2.l3.l4");
    576                         break;
    577 
    578                     case 4:
    579                         RT_FALL_THROUGH();
    580                     default:
    581                         rc = RTStrCat(szGroupSettings, sizeof(szGroupSettings), ".e.l.l2.l3.l4.f");
    582                         break;
    583                 }
    584 
    585                 if (RT_FAILURE(rc))
    586                     break;
    587             }
    588 
    589             LogRel(("Verbose log settings are: %s\n", szGroupSettings));
    590 
    591             if (RT_SUCCESS(rc))
    592                 rc = RTLogGroupSettings(g_pLoggerRelease, szGroupSettings);
    593             if (RT_FAILURE(rc))
    594                 RTMsgError("Setting log group settings failed, rc=%Rrc\n", rc);
    595         }
    596 
    597         /* Explicitly flush the log in case of VBOXTRAY_RELEASE_LOG=buffered. */
    598         RTLogFlush(g_pLoggerRelease);
    599     }
    600     else
    601         VBoxTrayShowError(ErrInfo.szMsg);
    602 
    603     return rc;
    604 }
    605 
    606 static void vboxTrayLogDestroy(void)
    607 {
    608     /* Only want to destroy the release logger before calling exit(). The debug
    609        logger can be useful after that point... */
    610     RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
    611 }
    612 
    613 /**
    614  * Displays an error message.
    615  *
    616  * @returns RTEXITCODE_FAILURE.
    617  * @param   pszFormat   The message text.
    618  * @param   ...         Format arguments.
    619  */
    620 RTEXITCODE VBoxTrayShowError(const char *pszFormat, ...)
    621 {
    622     va_list args;
    623     va_start(args, pszFormat);
    624     char *psz = NULL;
    625     RTStrAPrintfV(&psz, pszFormat, args);
    626     va_end(args);
    627 
    628     AssertPtr(psz);
    629     LogRel(("Error: %s", psz));
    630 
    631     MessageBox(GetDesktopWindow(), psz, "VBoxTray - Error", MB_OK | MB_ICONERROR);
    632 
    633     RTStrFree(psz);
    634 
    635     return RTEXITCODE_FAILURE;
    636 }
    637 
     499 * Destroys the invisible tool window of VBoxTray.
     500 */
    638501static void vboxTrayDestroyToolWindow(void)
    639502{
    640503    if (g_hwndToolWindow)
    641504    {
    642         Log(("Destroying tool window ...\n"));
    643 
    644505        /* Destroy the tool window. */
    645506        DestroyWindow(g_hwndToolWindow);
     
    650511}
    651512
     513/**
     514 * Creates the invisible tool window of VBoxTray.
     515 *
     516 * @returns VBox status code.
     517 */
    652518static int vboxTrayCreateToolWindow(void)
    653519{
     
    666532    {
    667533        dwErr = GetLastError();
    668         Log(("Registering invisible tool window failed, error = %08X\n", dwErr));
     534        VBoxTrayError("Registering invisible tool window failed, error = %08X\n", dwErr);
    669535    }
    670536    else
     
    697563    if (dwErr != ERROR_SUCCESS)
    698564         vboxTrayDestroyToolWindow();
    699     return RTErrConvertFromWin32(dwErr);
     565
     566    int const rc = RTErrConvertFromWin32(dwErr);
     567
     568    if (RT_FAILURE(rc))
     569        VBoxTrayError("Could not create tool window, rc=%Rrc\n", rc);
     570
     571    return rc;
    700572}
    701573
     
    782654        }
    783655    }
    784     return RTErrConvertFromWin32(dwErr);
     656
     657    int const rc = RTErrConvertFromWin32(dwErr);
     658
     659    if (RT_FAILURE(rc))
     660        VBoxTrayError("Could not setup seamless, rc=%Rrc\n", rc);
     661
     662    return rc;
    785663}
    786664
     
    800678}
    801679
     680/**
     681 * Main routine for starting / stopping all internal services.
     682 *
     683 * @returns VBox status code.
     684 */
    802685static int vboxTrayServiceMain(void)
    803686{
    804687    int rc = VINF_SUCCESS;
    805     LogFunc(("Entering vboxTrayServiceMain\n"));
     688    VBoxTrayVerbose(2, "Entering main loop\n");
    806689
    807690    g_hStopSem = CreateEvent(NULL, TRUE, FALSE, NULL);
     
    816699         * Start services listed in the vboxServiceTable.
    817700         */
    818         VBOXSERVICEENV svcEnv;
     701        VBOXTRAYSVCENV svcEnv;
    819702        svcEnv.hInstance = g_hInstance;
    820703
    821704        /* Initializes disp-if to default (XPDM) mode. */
    822705        VBoxDispIfInit(&svcEnv.dispIf); /* Cannot fail atm. */
    823     #ifdef VBOX_WITH_WDDM
     706#ifdef VBOX_WITH_WDDM
    824707        /*
    825708         * For now the display mode will be adjusted to WDDM mode if needed
    826709         * on display service initialization when it detects the display driver type.
    827710         */
    828     #endif
     711#endif
     712        VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_Init);
    829713
    830714        /* Finally start all the built-in services! */
    831715        rc = vboxTrayServicesStart(&svcEnv);
    832         if (RT_FAILURE(rc))
    833         {
    834             /* Terminate service if something went wrong. */
    835             vboxTrayServicesStop(&svcEnv);
    836         }
    837         else
     716        if (RT_SUCCESS(rc))
    838717        {
    839718            uint64_t const uNtVersion = RTSystemGetNtVersion();
    840             rc = vboxTrayCreateTrayIcon();
    841719            if (   RT_SUCCESS(rc)
    842720                && uNtVersion >= RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)) /* Only for W2K and up ... */
     
    853731            {
    854732                /* Report the host that we're up and running! */
    855                 hlpReportStatus(VBoxGuestFacilityStatus_Active);
     733                VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_Active);
    856734            }
    857735
     
    957835                LogFunc(("Returned from main loop, exiting ...\n"));
    958836            }
    959             LogFunc(("Waiting for services to stop ...\n"));
    960             vboxTrayServicesStop(&svcEnv);
     837
    961838        } /* Services started */
     839
     840        LogFunc(("Waiting for services to stop ...\n"));
     841
     842        VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_Terminating);
     843
     844        vboxTrayServicesStop(&svcEnv);
     845
    962846        CloseHandle(g_hStopSem);
     847
    963848    } /* Stop event created */
    964849
    965     vboxTrayRemoveTrayIcon();
    966 
    967     LogFunc(("Leaving with rc=%Rrc\n", rc));
     850    VBoxTrayVerbose(2, "Leaving main loop with %Rrc\n", rc);
    968851    return rc;
     852}
     853
     854/**
     855 * Attaches to a parent console (if any) or creates an own (dedicated) console window.
     856 *
     857 * @returns VBox status code.
     858 */
     859static int vboxTrayAttachConsole(void)
     860{
     861    if (g_fHasConsole) /* Console already attached? Bail out. */
     862        return VINF_SUCCESS;
     863
     864    /* As we run with the WINDOWS subsystem, we need to either attach to or create an own console
     865     * to get any stdout / stderr output. */
     866    bool fAllocConsole = false;
     867    if (!AttachConsole(ATTACH_PARENT_PROCESS))
     868        fAllocConsole = true;
     869
     870    if (fAllocConsole)
     871    {
     872        if (!AllocConsole())
     873            VBoxTrayShowError("Unable to attach to or allocate a console!");
     874        /* Continue running. */
     875    }
     876
     877    RTFILE hStdIn;
     878    RTFileFromNative(&hStdIn,  (RTHCINTPTR)GetStdHandle(STD_INPUT_HANDLE));
     879    /** @todo Closing of standard handles not support via IPRT (yet). */
     880    RTStrmOpenFileHandle(hStdIn, "r", 0, &g_pStdIn);
     881
     882    RTFILE hStdOut;
     883    RTFileFromNative(&hStdOut,  (RTHCINTPTR)GetStdHandle(STD_OUTPUT_HANDLE));
     884    /** @todo Closing of standard handles not support via IPRT (yet). */
     885    RTStrmOpenFileHandle(hStdOut, "wt", 0, &g_pStdOut);
     886
     887    RTFILE hStdErr;
     888    RTFileFromNative(&hStdErr,  (RTHCINTPTR)GetStdHandle(STD_ERROR_HANDLE));
     889    RTStrmOpenFileHandle(hStdErr, "wt", 0, &g_pStdErr);
     890
     891    if (!fAllocConsole) /* When attaching to the parent console, make sure we start on a fresh line. */
     892        RTPrintf("\n");
     893
     894    g_fHasConsole = true;
     895
     896    return VINF_SUCCESS;
     897}
     898
     899/**
     900 * Detaches from the (parent) console.
     901 */
     902static void vboxTrayDetachConsole()
     903{
     904    g_fHasConsole = false;
     905}
     906
     907/**
     908 * Destroys VBoxTray.
     909 *
     910 * @returns RTEXITCODE_SUCCESS.
     911 */
     912static RTEXITCODE vboxTrayDestroy()
     913{
     914    vboxTrayDetachConsole();
     915
     916    /* Release instance mutex. */
     917    if (g_hMutexAppRunning != NULL)
     918    {
     919        CloseHandle(g_hMutexAppRunning);
     920        g_hMutexAppRunning = NULL;
     921    }
     922
     923    return RTEXITCODE_SUCCESS;
     924}
     925
     926/**
     927 * Prints the help to either a message box or a console (if attached).
     928 *
     929 * @returns RTEXITCODE_SYNTAX.
     930 * @param   cArgs               Number of arguments given via argc.
     931 * @param   papszArgs           Arguments given specified by \a cArgs.
     932 */
     933static RTEXITCODE vboxTrayPrintHelp(int cArgs, char **papszArgs)
     934{
     935    RT_NOREF(cArgs);
     936
     937    char szServices[64] = { 0 };
     938    for (size_t i = 0; i < RT_ELEMENTS(g_aServices); i++)
     939    {
     940        char szName[RTTHREAD_NAME_LEN];
     941        int rc2 = RTStrCopy(szName, sizeof(szName), g_aServices[i].pDesc->pszName);
     942        RTStrToLower(szName); /* To make it easier for users to recognize the service name via command line. */
     943        AssertRCBreak(rc2);
     944        if (i > 0)
     945        {
     946            rc2 = RTStrCat(szServices, sizeof(szServices), ", ");
     947            AssertRCBreak(rc2);
     948        }
     949        rc2 = RTStrCat(szServices, sizeof(szServices), szName);
     950        AssertRCBreak(rc2);
     951    }
     952
     953    VBoxTrayShowMsgBox(VBOX_PRODUCT " - " VBOX_VBOXTRAY_TITLE,
     954                       MB_ICONINFORMATION,
     955                       VBOX_PRODUCT " %s v%u.%u.%ur%u\n"
     956                       "Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"
     957                       "Command Line Parameters:\n\n"
     958                       "-d, --debug\n"
     959                       "    Enables debugging mode\n"
     960                       "-f, --foreground\n"
     961                       "    Enables running in foreground\n"
     962                       "-l, --logfile <file>\n"
     963                       "    Enables logging to a file\n"
     964                       "-v, --verbose\n"
     965                       "    Increases verbosity\n"
     966                       "-V, --version\n"
     967                       "   Displays version number and exit\n"
     968                       "-?, -h, --help\n"
     969                       "   Displays this help text and exit\n"
     970                       "\n"
     971                       "Service parameters:\n\n"
     972                       "--enable-<service-name>\n"
     973                       "   Enables the given service\n"
     974                       "--disable-<service-name>\n"
     975                       "   Disables the given service\n"
     976                       "--only-<service-name>\n"
     977                       "   Only starts the given service\n"
     978                       "\n"
     979                       "Examples:\n"
     980                       "  %s -vvv --logfile C:\\Temp\\VBoxTray.log\n"
     981                       "  %s --foreground -vvvv --only-draganddrop\n"
     982                       "\n"
     983                       "Available services: %s\n\n",
     984                       VBOX_VBOXTRAY_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
     985                       papszArgs[0], papszArgs[0], szServices);
     986
     987    vboxTrayDestroy();
     988
     989    return RTEXITCODE_SYNTAX;
    969990}
    970991
     
    977998    if (RT_FAILURE(rc))
    978999        return RTMsgInitFailure(rc);
     1000
     1001    /* If a debugger is present, we always want to attach a console. */
     1002    if (IsDebuggerPresent())
     1003        vboxTrayAttachConsole();
    9791004
    9801005    /*
     
    9831008    static const RTGETOPTDEF s_aOptions[] =
    9841009    {
     1010        { "--debug",            'd',                         RTGETOPT_REQ_NOTHING },
     1011        { "/debug",             'd',                         RTGETOPT_REQ_NOTHING },
     1012        { "--foreground",       'f',                         RTGETOPT_REQ_NOTHING },
     1013        { "/foreground",        'f',                         RTGETOPT_REQ_NOTHING },
    9851014        { "--help",             'h',                         RTGETOPT_REQ_NOTHING },
    9861015        { "-help",              'h',                         RTGETOPT_REQ_NOTHING },
     
    10061035        {
    10071036            case 'h':
    1008                 hlpShowMessageBox(VBOX_PRODUCT " - " VBOX_VBOXTRAY_TITLE,
    1009                                   MB_ICONINFORMATION,
    1010                      "-- " VBOX_PRODUCT " %s v%u.%u.%ur%u --\n\n"
    1011                      "Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"
    1012                      "Command Line Parameters:\n\n"
    1013                      "-l, --logfile <file>\n"
    1014                      "    Enables logging to a file\n"
    1015                      "-v, --verbose\n"
    1016                      "    Increases verbosity\n"
    1017                      "-V, --version\n"
    1018                      "   Displays version number and exit\n"
    1019                      "-?, -h, --help\n"
    1020                      "   Displays this help text and exit\n"
    1021                      "\n"
    1022                      "Examples:\n"
    1023                      "  %s -vvv\n",
    1024                      VBOX_VBOXTRAY_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
    1025                      papszArgs[0], papszArgs[0]);
    1026                 return RTEXITCODE_SUCCESS;
     1037                return vboxTrayPrintHelp(cArgs, papszArgs);
     1038
     1039            case 'd':
     1040            {
     1041                /* ignore rc */ vboxTrayAttachConsole();
     1042                g_cVerbosity = 4; /* Set verbosity to level 4. */
     1043                break;
     1044            }
     1045
     1046            case 'f':
     1047            {
     1048                /* ignore rc */ vboxTrayAttachConsole();
     1049                /* Don't increase verbosity automatically here. */
     1050                break;
     1051            }
    10271052
    10281053            case 'l':
     1054            {
    10291055                if (*ValueUnion.psz == '\0')
    10301056                    szLogFile[0] = '\0';
     
    10331059                    rc = RTPathAbs(ValueUnion.psz, szLogFile, sizeof(szLogFile));
    10341060                    if (RT_FAILURE(rc))
    1035                         return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed on log file path: %Rrc (%s)",
    1036                                               rc, ValueUnion.psz);
     1061                    {
     1062                        int rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed on log file path: %Rrc (%s)",
     1063                                                    rc, ValueUnion.psz);
     1064                        vboxTrayDestroy();
     1065                        return rcExit;
     1066                    }
    10371067                }
    10381068                break;
     1069            }
    10391070
    10401071            case 'v':
     
    10431074
    10441075            case 'V':
    1045                 hlpShowMessageBox(VBOX_VBOXTRAY_TITLE, MB_ICONINFORMATION,
    1046                                   "Version: %u.%u.%ur%u",
    1047                                   VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
    1048                 return RTEXITCODE_SUCCESS;
     1076                VBoxTrayShowMsgBox(VBOX_PRODUCT " - " VBOX_VBOXTRAY_TITLE,
     1077                                   MB_ICONINFORMATION,
     1078                                   "%u.%u.%ur%u", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
     1079                return vboxTrayDestroy();
    10491080
    10501081            default:
    1051                 rc = RTGetOptPrintError(ch, &ValueUnion);
    1052                 break;
    1053         }
    1054     }
    1055 
    1056     /* Note: Do not use a global namespace ("Global\\") for mutex name here,
    1057      * will blow up NT4 compatibility! */
    1058     HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, VBOX_VBOXTRAY_TITLE);
    1059     if (   hMutexAppRunning != NULL
     1082            {
     1083                const  char *psz = ValueUnion.psz;
     1084                size_t const cch = strlen(ValueUnion.psz);
     1085                bool fFound = false;
     1086
     1087                if (cch > sizeof("--enable-") && !memcmp(psz, RT_STR_TUPLE("--enable-")))
     1088                    for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
     1089                        if ((fFound = !RTStrICmp(psz + sizeof("--enable-") - 1, g_aServices[j].pDesc->pszName)))
     1090                            g_aServices[j].fEnabled = true;
     1091
     1092                if (cch > sizeof("--disable-") && !memcmp(psz, RT_STR_TUPLE("--disable-")))
     1093                    for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
     1094                        if ((fFound = !RTStrICmp(psz + sizeof("--disable-") - 1, g_aServices[j].pDesc->pszName)))
     1095                            g_aServices[j].fEnabled = false;
     1096
     1097                if (cch > sizeof("--only-") && !memcmp(psz, RT_STR_TUPLE("--only-")))
     1098                    for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
     1099                    {
     1100                        g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("--only-") - 1, g_aServices[j].pDesc->pszName);
     1101                        if (g_aServices[j].fEnabled)
     1102                            fFound = true;
     1103                    }
     1104
     1105                if (!fFound)
     1106                {
     1107                    rc = vboxTrayServicesLazyPreInit();
     1108                    if (RT_FAILURE(rc))
     1109                        break;
     1110                    for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
     1111                    {
     1112                        rc = g_aServices[j].pDesc->pfnOption(NULL, cArgs, papszArgs, NULL);
     1113                        fFound = rc == VINF_SUCCESS;
     1114                        if (fFound)
     1115                            break;
     1116                        if (rc != -1) /* Means not parsed. */
     1117                            break;
     1118                    }
     1119                }
     1120                if (!fFound)
     1121                {
     1122                    RTGetOptPrintError(ch, &ValueUnion); /* Only shown on console. */
     1123                    return vboxTrayPrintHelp(cArgs, papszArgs);
     1124                }
     1125
     1126                continue;
     1127            }
     1128        }
     1129    }
     1130
     1131    if (RT_FAILURE(rc))
     1132    {
     1133        vboxTrayDestroy();
     1134        return RTEXITCODE_FAILURE;
     1135    }
     1136
     1137    /**
     1138     * VBoxTray already running? Bail out.
     1139     *
     1140     * Note: Do not use a global namespace ("Global\\") for mutex name here,
     1141     * will blow up NT4 compatibility!
     1142     */
     1143    g_hMutexAppRunning = CreateMutex(NULL, FALSE, VBOX_VBOXTRAY_TITLE);
     1144    if (   g_hMutexAppRunning != NULL
    10601145        && GetLastError() == ERROR_ALREADY_EXISTS)
    10611146    {
    1062         /* VBoxTray already running? Bail out. */
    1063         CloseHandle (hMutexAppRunning);
    1064         hMutexAppRunning = NULL;
    1065         return RTEXITCODE_SUCCESS;
    1066     }
     1147        VBoxTrayError(VBOX_VBOXTRAY_TITLE " already running!\n");
     1148        return vboxTrayDestroy();
     1149    }
     1150
     1151    /* Set the instance handle. */
     1152#ifdef IPRT_NO_CRT
     1153    Assert(g_hInstance == NULL); /* Make sure this isn't set before by WinMain(). */
     1154    g_hInstance = GetModuleHandleW(NULL);
     1155#endif
    10671156
    10681157    rc = VbglR3Init();
    10691158    if (RT_SUCCESS(rc))
    10701159    {
    1071         rc = vboxTrayLogCreate(szLogFile[0] ? szLogFile : NULL);
     1160        rc = VBoxTrayLogCreate(szLogFile[0] ? szLogFile : NULL);
    10721161        if (RT_SUCCESS(rc))
    10731162        {
    1074             LogRel(("Verbosity level: %d\n", g_cVerbosity));
    1075 
    1076             /* Log the major windows NT version: */
    1077             uint64_t const uNtVersion = RTSystemGetNtVersion();
    1078             LogRel(("Windows version %u.%u build %u (uNtVersion=%#RX64)\n", RTSYSTEM_NT_VERSION_GET_MAJOR(uNtVersion),
    1079                     RTSYSTEM_NT_VERSION_GET_MINOR(uNtVersion), RTSYSTEM_NT_VERSION_GET_BUILD(uNtVersion), uNtVersion ));
    1080 
    1081             /* Set the instance handle. */
    1082 #ifdef IPRT_NO_CRT
    1083             Assert(g_hInstance == NULL); /* Make sure this isn't set before by WinMain(). */
    1084             g_hInstance = GetModuleHandleW(NULL);
    1085 #endif
    1086             hlpReportStatus(VBoxGuestFacilityStatus_Init);
     1163            VBoxTrayInfo("Verbosity level: %d\n", g_cVerbosity);
     1164
    10871165            rc = vboxTrayCreateToolWindow();
     1166            if (RT_SUCCESS(rc))
     1167                rc = vboxTrayCreateTrayIcon();
     1168
     1169            VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_PreInit);
     1170
    10881171            if (RT_SUCCESS(rc))
    10891172            {
     
    11011184
    11021185                rc = vboxDtInit();
    1103                 if (!RT_SUCCESS(rc))
     1186                if (RT_FAILURE(rc))
    11041187                {
    1105                     LogFlowFunc(("vboxDtInit failed, rc=%Rrc\n", rc));
    11061188                    /* ignore the Dt Init failure. this can happen for < XP win that do not support WTS API
    11071189                     * in that case the session is treated as active connected to the physical console
     
    11101192                }
    11111193
    1112                 rc = VBoxAcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_SEAMLESS | VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, true);
    1113                 if (!RT_SUCCESS(rc))
    1114                     LogFlowFunc(("VBoxAcquireGuestCaps failed with rc=%Rrc, ignoring ...\n", rc));
    1115 
    1116                 rc = vboxTraySetupSeamless(); /** @todo r=andy Do we really want to be this critical for the whole application? */
    1117                 if (RT_SUCCESS(rc))
    1118                 {
    1119                     rc = vboxTrayServiceMain();
    1120                     if (RT_SUCCESS(rc))
    1121                         hlpReportStatus(VBoxGuestFacilityStatus_Terminating);
    1122                     vboxTrayShutdownSeamless();
    1123                 }
     1194                VBoxAcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_SEAMLESS | VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, true);
     1195
     1196                vboxTraySetupSeamless();
     1197
     1198                rc = vboxTrayServiceMain();
     1199                /* Note: Do *not* overwrite rc in the following code, as this acts as the exit code. */
     1200
     1201                vboxTrayShutdownSeamless();
    11241202
    11251203                /* it should be safe to call vboxDtTerm even if vboxStInit above failed */
     
    11301208
    11311209                VBoxCapsTerm();
    1132 
    1133                 vboxTrayDestroyToolWindow();
    1134             }
     1210            }
     1211
     1212            vboxTrayRemoveTrayIcon();
     1213            vboxTrayDestroyToolWindow();
     1214
    11351215            if (RT_SUCCESS(rc))
    1136                 hlpReportStatus(VBoxGuestFacilityStatus_Terminated);
     1216
     1217                VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_Terminated);
    11371218            else
    1138             {
    1139                 LogRel(("Error while starting, rc=%Rrc\n", rc));
    1140                 hlpReportStatus(VBoxGuestFacilityStatus_Failed);
    1141             }
    1142 
    1143             LogRel(("Ended\n"));
    1144 
    1145             vboxTrayLogDestroy();
     1219                VBoxTrayHlpReportStatus(VBoxGuestFacilityStatus_Failed);
     1220
     1221            VBoxTrayInfo("VBoxTray terminated with %Rrc\n", rc);
     1222
     1223            VBoxTrayLogDestroy();
    11461224        }
    11471225
     
    11511229        VBoxTrayShowError("VbglR3Init failed: %Rrc\n", rc);
    11521230
    1153     /* Release instance mutex. */
    1154     if (hMutexAppRunning != NULL)
    1155     {
    1156         CloseHandle(hMutexAppRunning);
    1157         hMutexAppRunning = NULL;
    1158     }
     1231    vboxTrayDestroy();
    11591232
    11601233    return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
     
    12131286            {
    12141287                case TIMERID_VBOXTRAY_CHECK_HOSTVERSION:
     1288                {
    12151289                    if (RT_SUCCESS(VBoxCheckHostVersion()))
    12161290                    {
    1217                         /* After successful run we don't need to check again. */
     1291                        /* After a successful run we don't need to check again. */
    12181292                        KillTimer(g_hwndToolWindow, TIMERID_VBOXTRAY_CHECK_HOSTVERSION);
    12191293                    }
     1294
    12201295                    return 0;
     1296                }
    12211297
    12221298                default:
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