VirtualBox

Changeset 71139 in vbox for trunk/src/VBox


Ignore:
Timestamp:
Feb 27, 2018 5:48:13 PM (7 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
121027
Message:

Main/VBoxSVC,VBoxSDS: fix for ​bugref:8161: added API client list interface to VBoxSDS, forcibly closes all VirtualBox clients process on Windows 7 at system shutdown. Added functionality to close VBoxSDS and VBoxSVC services automatically if the API client finished unexpectedly (crashed).

Location:
trunk/src/VBox/Main
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Main/Makefile.kmk

    r70880 r71139  
    297297        src-all/VirtualBoxBase.cpp \
    298298        src-all/VirtualBoxErrorInfoImpl.cpp \
     299    src-all/VirtualBoxClientListImpl.cpp \
    299300        src-global/VirtualBoxSDSImpl.cpp
    300301 VBoxSDS_SOURCES.win = \
     
    12011202 VBoxProxyStub_SDKS     = VBOX_NTDLL
    12021203 VBoxProxyStub_DEFPATH  = $(VBoxCOM_0_OUTDIR)/# Hack to workaround drive letter in $(VBoxCOM_0_OUTDIR)/ for CFLAGS hack below.
    1203  VBoxProxyStub_INCS     = $(VBoxCOM_0_OUTDIR)/
     1204 VBoxProxyStub_INCS     = $(VBoxCOM_0_OUTDIR)/ \
     1205    $(VBOX_PATH_MAIN_SRC)/include/
    12041206 VBoxProxyStub_SOURCES := \
    12051207        VirtualBox_p.c \
     
    12071209        $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.def \
    12081210        $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.rc \
    1209         $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.c
     1211    $(PATH_SUB_CURRENT)/src-all/win/RpcChannelHook.cpp \
     1212    $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.c
    12101213 src-all/win/VBoxProxyStub.rc_DEPS = $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
    12111214 VBoxProxyStub_VirtualBox_p.c_CFLAGS.x86 = /Gz # Workaround for incorrect ObjectStublessClient* prototypes in SDK v7.1.
     
    12151218 VBoxProxyStubLegacy_EXTENDS  = VBoxProxyStub
    12161219 VBoxProxyStubLegacy_DEFS     = $(VBoxProxyStub_DEFS) VBOX_IN_PROXY_STUB_LEGACY
    1217  VBoxProxyStubLegacy_INCS     = $(VBoxCOM_0_OUTDIR)/legacy/
     1220 VBoxProxyStubLegacy_INCS     = $(VBoxCOM_0_OUTDIR)/legacy/ \
     1221    $(VBOX_PATH_MAIN_SRC)/include/
    12181222 VBoxProxyStubLegacy_SOURCES  = \
    12191223        $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_p.c \
     
    12211225        src-all/win/VBoxProxyStub.def \
    12221226        src-all/win/VBoxProxyStubLegacy.rc \
    1223         src-all/win/VBoxProxyStub.c
     1227    src-all/win/RpcChannelHook.cpp \
     1228        src-all/win/VBoxProxyStub.c
    12241229 src-all/win/VBoxProxyStubLegacy.rc_DEPS = $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.tlb
    12251230
     
    12291234  VBoxProxyStub-x86_SDKS     = VBOX_NTDLL
    12301235  VBoxProxyStub-x86_DEFS     = $(VBoxProxyStub_DEFS) VBOX_PROXY_STUB_32_ON_64 WIN32
    1231   VBoxProxyStub-x86_INCS     = $(VBoxCOM-x86_0_OUTDIR)/
     1236  VBoxProxyStub-x86_INCS     = $(VBoxCOM-x86_0_OUTDIR)/ \
     1237    $(VBOX_PATH_MAIN_SRC)/include/
    12321238  VBoxProxyStub-x86_SOURCES  = \
    12331239        $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_p.c \
     
    12351241        src-all/win/VBoxProxyStub.c \
    12361242        src-all/win/VBoxProxyStub-x86.rc \
     1243    src-all/win/RpcChannelHook.cpp \
    12371244        src-all/win/VBoxProxyStub.def
    12381245  src-all/win/VBoxProxyStub-x86.rc_DEPS = $(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb
  • trunk/src/VBox/Main/idl/VirtualBox.xidl

    r71108 r71139  
    2448124481      </method>
    2448224482
     24483      <method name="notifyClientsFinished">
     24484          <desc>Notify service that their clients finished.</desc>
     24485      </method>
     24486
    2448324487  </interface>
    2448424488
     
    2453024534      </method>
    2453124535
     24536      <method name="notifyClientsFinished">
     24537          <desc>Notify SDS that clients finished.</desc>
     24538      </method>
     24539
    2453224540  </interface>
     24541
     24542    <!--
     24543  // IVirtualBoxClientList
     24544  //////////////////////////////////////////////////////////////////////////
     24545  -->
     24546
     24547    <interface
     24548      name="IVirtualBoxClientList" extends="$unknown"
     24549      uuid="6e7f09c9-da13-4126-9cc8-9aab81fed413"
     24550      wsmap="managed"
     24551      reservedMethods="2" reservedAttributes="2"
     24552    >
     24553        <desc>
     24554            The IVirtualBoxClientList interface represents a list of VirtualBox API clients.
     24555        </desc>
     24556
     24557        <method name="registerClient">
     24558            <desc>Register VirtualBox API Client.</desc>
     24559            <param name="pid" type="long" dir="in">
     24560                <desc>Process ID of VirtualBox API client.</desc>
     24561            </param>
     24562        </method>
     24563
     24564        <attribute name="clients" readonly="yes" type="long" dir="out" safearray="yes">
     24565            <desc>List of registered VirtualBox API clients.</desc>
     24566        </attribute>
     24567
     24568    </interface>
    2453324569
    2453424570</if> <!-- bird: shouldn't this if-element at leat include the VBoxSDS module, if not the whole system service application? -->
     
    2454424580     -->
    2454524581  <module name="VBoxSDS" context="LocalService">
    24546     <class name="VirtualBoxSDS" uuid="74ab5ffe-8726-4435-aa7e-876d705bcba5"
    24547            namespace="virtualbox.org">
    24548       <interface name="IVirtualBoxSDS" default="yes"/>
    24549     </class>
     24582      <class name="VirtualBoxSDS" uuid="74ab5ffe-8726-4435-aa7e-876d705bcba5"
     24583             namespace="virtualbox.org">
     24584          <interface name="IVirtualBoxSDS" default="yes"/>
     24585      </class>
     24586      <class name="VirtualBoxClientList" uuid="b2313c29-07eb-4613-ab27-f86bda7a0cae" namespace="virtualbox.org" >
     24587          <interface name="IVirtualBoxClientList" default="yes" />
     24588      </class>
    2455024589  </module>
    2455124590
  • trunk/src/VBox/Main/include/VirtualBoxSDSImpl.h

    r70129 r71139  
    7474    STDMETHODIMP_(HRESULT) RegisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid, IUnknown **aExistingVirtualBox);
    7575    STDMETHODIMP_(HRESULT) DeregisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid);
     76    STDMETHODIMP_(HRESULT) NotifyClientsFinished();
    7677
    7778
  • trunk/src/VBox/Main/src-all/win/VBoxProxyStub.c

    r70240 r71139  
    4141#include <iprt/uuid.h>
    4242#include <iprt/utf16.h>
     43#include "RpcChannelHook.h"
    4344
    4445
     
    147148};
    148149
     150BOOL IsVBoxServiceProcess(void)
     151{
     152    if (GetEnvironmentVariable(L"VBOX_SERVICE_PROCESS", NULL, 0) == 0)
     153    {
     154        int res = GetLastError();
     155        if (res != ERROR_ENVVAR_NOT_FOUND)
     156            LogRel(("Error: cannot get service environment variable: %Rrwa\n", res));
     157        return false;
     158    }
     159    return true;
     160}
     161
    149162
    150163/**
     
    171184            RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
    172185            Log12(("VBoxProxyStub[%u]/DllMain: DLL_PROCESS_ATTACH\n", GetCurrentProcessId()));
     186
     187            /* Install RPC channel hook to intercept a moment just after VirtualBox object activation.
     188               It's reports to VBoxSDS that a new VirtualBox API client started. */
     189            if(!IsVBoxServiceProcess())
     190                SetupClientRpcChannelHook();
    173191
    174192#ifdef VBOX_STRICT
     
    13461364    VbpsRegisterClassId(pState, &CLSID_VirtualBoxSDS, "VirtualBoxSDS Class", pszSdsAppId, "VirtualBox.VirtualBoxSDS", ".1",
    13471365                        &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszSdsExe, NULL /*N/A*/);
     1366
     1367    VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClientList.1", "VirtualBoxClientList Class", &CLSID_VirtualBoxClientList, NULL);
     1368    VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClientList", "VirtualBoxClientList Class", &CLSID_VirtualBoxClientList, ".1");
     1369    VbpsRegisterClassId(pState, &CLSID_VirtualBoxClientList, "VirtualBoxClientList Class", pszSdsAppId, "VirtualBox.VirtualBoxClientList", ".1",
     1370        &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszSdsExe, NULL /*N/A*/);
    13481371#endif
    13491372}
  • trunk/src/VBox/Main/src-global/VirtualBoxSDSImpl.cpp

    r70507 r71139  
    5353     * This is NULL if not set. */
    5454    ComPtr<IVBoxSVCRegistration>    m_ptrTheChosenOne;
     55    ComPtr<IVirtualBoxClientList>   m_ptrClientList;
    5556private:
    5657    /** Reference count to make destruction safe wrt hung callers.
     
    178179                        LogRel(("VirtualBoxSDS::registerVBoxSVC: Seems VBoxSVC instance died.  Dropping it and letting caller take over.\n"));
    179180                        pUserData->m_ptrTheChosenOne.setNull();
     181                        /* Release the client list and stop client list watcher thread*/
     182                        pUserData->m_ptrClientList.setNull();
    180183                    }
    181184                }
     
    190193                    {
    191194                        pUserData->m_ptrTheChosenOne = aVBoxSVC;
     195                        /*
     196                        * Create instance of ClientList
     197                        */
     198                        HRESULT hrc = CoCreateInstance(CLSID_VirtualBoxClientList, NULL, CLSCTX_LOCAL_SERVER,
     199                            IID_IVirtualBoxClientList,
     200                            (void **)pUserData->m_ptrClientList.asOutParam());
     201                        if (SUCCEEDED(hrc))
     202                        {
     203                            LogFunc(("Created API client list instance in VBoxSDS : hr=%Rhrf\n", hrc));
     204                        }
     205                        else
     206                        {
     207                            LogFunc(("Error in creating API client list instance: hr=%Rhrf\n", hrc));
     208                        }
     209
    192210                        hrc = S_OK;
    193211                    }
     
    235253                    pUserData->m_ptrTheChosenOne.setNull();
    236254                    /** @todo consider evicting the user from the table...   */
     255                    /* Release the client list and stop client list watcher thread*/
     256                    pUserData->m_ptrClientList.setNull();
    237257                }
    238258                else
     
    260280}
    261281
     282
     283STDMETHODIMP_(HRESULT) VirtualBoxSDS::NotifyClientsFinished()
     284{
     285    LogRelFlowThisFuncEnter();
     286
     287    int vrc = RTCritSectRwEnterShared(&m_MapCritSect);
     288    if (RT_SUCCESS(vrc))
     289    {
     290        for (UserDataMap_T::iterator it = m_UserDataMap.begin(); it != m_UserDataMap.end(); ++it)
     291        {
     292            VBoxSDSPerUserData *pUserData = it->second;
     293            if (pUserData && pUserData->m_ptrTheChosenOne)
     294            {
     295                LogRelFunc(("Notify VBoxSVC that all clients finished\n"));
     296                /* Notify VBoxSVC about finishing all API clients it should free references to VBoxSDS
     297                   and clean up itself */
     298                if (pUserData->m_ptrClientList)
     299                    pUserData->m_ptrClientList.setNull();
     300                pUserData->m_ptrTheChosenOne->NotifyClientsFinished();
     301            }
     302        }
     303        RTCritSectRwLeaveShared(&m_MapCritSect);
     304    }
     305
     306    LogRelFlowThisFuncLeave();
     307    return S_OK;
     308}
    262309
    263310// private methods
  • trunk/src/VBox/Main/src-global/win/VBoxSDS.cpp

    r70684 r71139  
    8787
    8888#include "VirtualBoxSDSImpl.h"
     89#include "VirtualBoxClientListImpl.h"
    8990#include "Logging.h"
    9091
     
    694695
    695696
     697int SetServiceEnvFlag()
     698{
     699    int rc = VINF_SUCCESS;
     700    if (!SetEnvironmentVariable(L"VBOX_SERVICE_PROCESS", L""))
     701    {
     702        rc = RTErrConvertFromWin32(GetLastError());
     703        LogRel(("Error: cannot set service environment flag:  %Rrs\n", rc));
     704    }
     705    return rc;
     706}
     707
     708
    696709/**
    697710 * Main function for the VBoxSDS process.
     
    715728     */
    716729    RTR3InitExe(argc, &argv, 0);
     730
     731    SetServiceEnvFlag();
    717732
    718733    static const RTGETOPTDEF s_aOptions[] =
     
    922937            BEGIN_OBJECT_MAP(s_aObjectMap)
    923938                OBJECT_ENTRY(CLSID_VirtualBoxSDS, VirtualBoxSDS)
     939                OBJECT_ENTRY(CLSID_VirtualBoxClientList, VirtualBoxClientList)
    924940            END_OBJECT_MAP()
    925941            hrcExit = pServiceModule->init(s_aObjectMap, hInstance, &LIBID_VirtualBox,
  • trunk/src/VBox/Main/src-server/win/svcmain.cpp

    r70030 r71139  
    2828#include "VBox/com/com.h"
    2929#include "VBox/com/VirtualBox.h"
     30#include "VBox/com/array.h"
    3031
    3132#include "VirtualBoxImpl.h"
     
    4445#include <iprt/asm.h>
    4546
     47#include <TlHelp32.h>
    4648
    4749/*********************************************************************************************************************************
     
    9496volatile uint32_t dwTimeOut = dwNormalTimeout; /* time for EXE to be idle before shutting down. Can be decreased at system shutdown phase. */
    9597
     98
     99BOOL CALLBACK CloseWindowProc(_In_ HWND   hWnd, _In_ LPARAM /* lParam */)
     100{
     101    _ASSERTE(hWnd);
     102    DWORD_PTR dwResult;
     103    // Close topmost windows in the thread
     104    LRESULT lResult = SendMessageTimeout(hWnd, WM_CLOSE, NULL, NULL,
     105        SMTO_ABORTIFHUNG | SMTO_BLOCK, 0, &dwResult);
     106    if (lResult != 0)
     107    {
     108        LogRel(("EnumThreadWndProc: Close message sent to window %x successfully \n", hWnd));
     109    }
     110    else
     111    {
     112        LogRel(("EnumThreadWndProc: Cannot send event to window %x. result: %d, last error: %x\n",
     113            hWnd, dwResult, GetLastError()));
     114    }
     115    return TRUE;
     116}
     117
     118void SendCloseToAllThreads(DWORD dwTargetPid)
     119{
     120    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
     121    if (hSnapshot == NULL)
     122    {
     123        LogRel(("SendCloseToAllThreads: cannot get threads snapshot. error: 0x%x \n",
     124            GetLastError()));
     125        return;
     126    }
     127
     128    THREADENTRY32 threadEntry;
     129    ZeroMemory(&threadEntry, sizeof(threadEntry));
     130    threadEntry.dwSize = sizeof(threadEntry);
     131
     132    if (Thread32First(hSnapshot, &threadEntry))
     133    {
     134        do
     135        {
     136            LogRel(("SendCloseToAllThreads: process: %d thread: %x \n",
     137                threadEntry.th32OwnerProcessID, threadEntry.th32ThreadID));
     138            if (threadEntry.th32OwnerProcessID == dwTargetPid)
     139            {
     140                BOOL bRes = EnumThreadWindows(threadEntry.th32ThreadID, CloseWindowProc, NULL);
     141                if (!bRes)
     142                {
     143                    LogRel(("SendCloseToAllThreads: EnumThreadWindows() failed to enumerate threads. error: %x \n",
     144                        GetLastError()));
     145                }
     146                else
     147                {
     148                    LogRel(("SendCloseToAllThreads: about to close window in thread %x of process d\n",
     149                        threadEntry.th32ThreadID, dwTargetPid));
     150                }
     151            }
     152        } while (Thread32Next(hSnapshot, &threadEntry));
     153    }
     154    CloseHandle(hSnapshot);
     155}
     156
     157static int CloseActiveClients()
     158{
     159    ComPtr<IVirtualBoxClientList> ptrClientList;
     160    /*
     161    * Connect to VBoxSDS.
     162    */
     163    // TODO: here we close all API client processes: our own and customers
     164    LogRelFunc(("Forcibly close API clients during system shutdown on Windows 7:\n"));
     165    HRESULT hrc = CoCreateInstance(CLSID_VirtualBoxClientList, NULL, CLSCTX_LOCAL_SERVER, IID_IVirtualBoxClientList,
     166        (void **)ptrClientList.asOutParam());
     167    if (SUCCEEDED(hrc))
     168    {
     169        com::SafeArray<LONG> aCllients;
     170        hrc = ptrClientList->get_Clients(aCllients.__asOutParam());
     171        RTCList<LONG> clientsList = aCllients.toList();
     172        LogRel(("==========Client list begin ========\n"));
     173        for (int i = 0; i < clientsList.size(); i++)
     174        {
     175            LogRel(("About to close client pid: %d\n", clientsList[i]));
     176            SendCloseToAllThreads(clientsList[i]);
     177        }
     178        LogRel(("==========Client list end ========\n"));
     179    }
     180    else
     181    {
     182        LogFunc(("Error to connect to VBoxSDS: hr=%Rhrf\n", hrc));
     183    }
     184    return 0;
     185}
     186
     187// These are copies of functions defined in VersionHelpers.h
     188bool IsWindowsVersionOrGreaterWrap(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
     189{
     190    OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0,{ 0 }, 0, 0 };
     191    DWORDLONG        const dwlConditionMask = VerSetConditionMask(
     192        VerSetConditionMask(
     193            VerSetConditionMask(
     194                0, VER_MAJORVERSION, VER_GREATER_EQUAL),
     195            VER_MINORVERSION, VER_GREATER_EQUAL),
     196        VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
     197
     198    osvi.dwMajorVersion = wMajorVersion;
     199    osvi.dwMinorVersion = wMinorVersion;
     200    osvi.wServicePackMajor = wServicePackMajor;
     201
     202    return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
     203}
     204
     205
     206#if !defined _WIN32_WINNT_WIN8
     207
     208#define _WIN32_WINNT_WIN8                   0x0602
     209
     210#endif  // #if !defined _WIN32_WINNT_WIN8
     211
     212bool IsWindows8OrGreaterWrap()
     213{
     214    return IsWindowsVersionOrGreaterWrap(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
     215}
    96216
    97217
     
    241361    HRESULT VirtualBoxClassFactory::i_registerWithSds(IUnknown **ppOtherVirtualBox);
    242362    void    VirtualBoxClassFactory::i_deregisterWithSds(void);
     363    void    VirtualBoxClassFactory::i_finishVBoxSvc();
    243364
    244365    friend VBoxSVCRegistration;
     
    311432            return m_pFactory->i_getVirtualBox(ppResult);
    312433        return E_FAIL;
     434    }
     435
     436    // IVBoxSVCRegistration: called from
     437    STDMETHOD(NotifyClientsFinished)()
     438    {
     439        LogRelFunc(("All clients gone - shutdown sequence initiated\n"));
     440
     441        m_pFactory->i_finishVBoxSvc();
     442
     443        // This is not enough to finish VBoxSvc such as reference to crashed client still is in action
     444        // So I forcebly shutdown VBoxSvc
     445        while (g_pModule->Unlock() > 0)
     446        {};
     447
     448        return S_OK;
    313449    }
    314450};
     
    354490            NOREF(hrc);
    355491        }
    356         m_ptrVirtualBoxSDS.setNull();
    357         g_fRegisteredWithVBoxSDS = false;
    358     }
    359     if (m_pVBoxSVC)
    360     {
    361         m_pVBoxSVC->m_pFactory = NULL;
    362         m_pVBoxSVC->Release();
    363         m_pVBoxSVC = NULL;
    364     }
     492    }
     493    i_finishVBoxSvc();
    365494}
    366495
     
    381510    Log(("VirtualBoxClassFactory::GetVirtualBox: E_FAIL\n"));
    382511    return E_FAIL;
     512}
     513
     514
     515void    VirtualBoxClassFactory::i_finishVBoxSvc()
     516{
     517    LogRelFunc(("Finish work of VBoxSVc and VBoxSDS\n"));
     518    if (m_ptrVirtualBoxSDS.isNotNull())
     519    {
     520        m_ptrVirtualBoxSDS.setNull();
     521        g_fRegisteredWithVBoxSDS = false;
     522    }
     523    if (m_pVBoxSVC)
     524    {
     525        m_pVBoxSVC->m_pFactory = NULL;
     526        m_pVBoxSVC->Release();
     527        m_pVBoxSVC = NULL;
     528    }
    383529}
    384530
     
    536682                    Log(("VBoxSVCWinMain: WM_QUERYENDSESSION: VBoxSvc has active connections. bActivity = %d. Loc count = %d\n",
    537683                         g_pModule->bActivity, g_pModule->GetLockCount()));
     684
     685                    // On Windows 7 our clients doesn't receive right sequence of Session End events
     686                    // So we send them all WM_QUIT to forcible close them.
     687                    // Windows 10 sends end session events correctly
     688                    // Note: the IsWindows8Point1() and IsWindows10OrGreater() doesnt work in
     689                    // application without manifest so I use old compatible functions for detection of Win 7
     690                    if(!IsWindows8OrGreaterWrap())
     691                        CloseActiveClients();
    538692                }
    539693                rc = !fActiveConnection;
     
    629783        }
    630784    }
     785}
     786
     787
     788int SetServiceEnvFlag()
     789{
     790    int rc = VINF_SUCCESS;
     791    if (!SetEnvironmentVariable(L"VBOX_SERVICE_PROCESS", L""))
     792    {
     793        rc = RTErrConvertFromWin32(GetLastError());
     794        LogRel(("Error: cannot set service environment flag:  %Rrs\n", rc));
     795    }
     796    return rc;
    631797}
    632798
     
    667833     */
    668834    RTR3InitExe(argc, &argv, 0);
     835
     836    SetServiceEnvFlag();
    669837
    670838    static const RTGETOPTDEF s_aOptions[] =
     
    8731041            if (RT_FAILURE(vrc))
    8741042            {
    875                 Log(("SVCMAIN: Failed to process Helper request (%Rrc).", vrc));
     1043                Log(("SVCMAIN: Failed to process Helper request (%Rrc).\n", vrc));
    8761044                nRet = 1;
    8771045            }
Note: See TracChangeset for help on using the changeset viewer.

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