VirtualBox

Changeset 24703 in vbox for trunk


Ignore:
Timestamp:
Nov 16, 2009 3:50:47 PM (15 years ago)
Author:
vboxsync
Message:

Main,GuestProperties: Moved the HGCM shutdown down after we've powered off the VM. Ditto for moving the guest properties to VBoxSVC. Explicitly flush the guest property change notifications before moving them. Added a handleUnexpectedExceptions to Console that is similar to the one found in VirtualBox (i.e. in VBoxSVC).

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/VBox/HostServices/GuestPropertySvc.h

    r24666 r24703  
    245245     * The parameter format matches that of ENUM_PROPS.
    246246     */
    247     ENUM_PROPS_HOST = 6
     247    ENUM_PROPS_HOST = 6,
     248
     249    /**
     250     * Flush notifications.
     251     * Takes one 32-bit unsigned integer parameter that gives the number of
     252     * milliseconds to wait for the worker thread to get the work done.
     253     */
     254    FLUSH_NOTIFICATIONS_HOST
    248255};
    249256
  • trunk/src/VBox/HostServices/GuestProperties/service.cpp

    r24665 r24703  
    2121 */
    2222
    23 /**
     23/** @page pg_svc_guest_properties   Guest Property HGCM Service
     24 *
    2425 * This HGCM service allows the guest to set and query values in a property
    2526 * store on the host.  The service proxies the guest requests to the service
     
    3940 */
    4041
    41 #define LOG_GROUP LOG_GROUP_HGCM
    42 
    4342/*******************************************************************************
    4443*   Header Files                                                               *
    4544*******************************************************************************/
     45#define LOG_GROUP LOG_GROUP_HGCM
    4646#include <VBox/HostServices/GuestPropertySvc.h>
    4747
    4848#include <VBox/log.h>
     49#include <iprt/asm.h>
     50#include <iprt/assert.h>
     51#include <iprt/autores.h>
     52#include <iprt/cpputils.h>
    4953#include <iprt/err.h>
    50 #include <iprt/assert.h>
     54#include <iprt/mem.h>
     55#include <iprt/req.h>
    5156#include <iprt/string.h>
    52 #include <iprt/mem.h>
    53 #include <iprt/autores.h>
     57#include <iprt/thread.h>
    5458#include <iprt/time.h>
    55 #include <iprt/cpputils.h>
    56 #include <iprt/req.h>
    57 #include <iprt/thread.h>
    5859
    5960#include <memory>  /* for auto_ptr */
     
    163164    /** Queue of outstanding property change notifications */
    164165    RTREQQUEUE *mReqQueue;
     166    /** Request that we've left pending in a call to flushNotifications. */
     167    PRTREQ mPendingDummyReq;
    165168    /** Thread for processing the request queue */
    166169    RTTHREAD mReqThread;
    167170    /** Tell the thread that it should exit */
    168     bool mfExitThread;
     171    bool volatile mfExitThread;
    169172    /** Callback function supplied by the host for notification of updates
    170173     * to properties */
     
    222225public:
    223226    explicit Service(PVBOXHGCMSVCHELPERS pHelpers)
    224         : mpHelpers(pHelpers), mfExitThread(false), mpfnHostCallback(NULL),
    225           mpvHostData(NULL)
     227        : mpHelpers(pHelpers)
     228        , mPendingDummyReq(NULL)
     229        , mfExitThread(false)
     230        , mpfnHostCallback(NULL)
     231        , mpvHostData(NULL)
    226232    {
    227233        int rc = RTReqCreateQueue(&mReqQueue);
     
    321327    int delProperty(uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool isGuest);
    322328    int enumProps(uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
     329    int flushNotifications(uint32_t cMsTimeout);
    323330    int getNotification(VBOXHGCMCALLHANDLE callHandle, uint32_t cParms,
    324331                        VBOXHGCMSVCPARM paParms[]);
     
    350357 * @copydoc FNRTTHREAD
    351358 */
     359/* static */
    352360DECLCALLBACK(int) Service::reqThreadFn(RTTHREAD ThreadSelf, void *pvUser)
    353361{
     
    819827            rc = VERR_BUFFER_OVERFLOW;
    820828    }
     829    return rc;
     830}
     831
     832/**
     833 * Flushes the notifications.
     834 *
     835 * @returns iprt status value
     836 * @param   cMsTimeout  The timeout in milliseconds.
     837 * @thread  HGCM
     838 */
     839int Service::flushNotifications(uint32_t cMsTimeout)
     840{
     841    LogFlowThisFunc(("cMsTimeout=%RU32\n", cMsTimeout));
     842    int rc;
     843
     844#ifndef VBOX_GUEST_PROP_TEST_NOTHREAD
     845    /*
     846     * Wait for the thread to finish processing all current requests.
     847     */
     848    if (!mPendingDummyReq && !RTReqIsBusy(mReqQueue))
     849        rc = VINF_SUCCESS;
     850    else
     851    {
     852        if (!mPendingDummyReq)
     853            rc = RTReqCallEx(mReqQueue, &mPendingDummyReq, 0 /*cMillies*/, RTREQFLAGS_VOID, (PFNRT)reqVoid, 0);
     854        else
     855            rc = VERR_TIMEOUT;
     856        if (rc == VERR_TIMEOUT)
     857            rc = RTReqWait(mPendingDummyReq, cMsTimeout);
     858        if (RT_SUCCESS(rc))
     859        {
     860            RTReqFree(mPendingDummyReq);
     861            mPendingDummyReq = NULL;
     862        }
     863    }
     864#else
     865    NOREF(cMsTimeout);
     866    rc = VINF_SUCCESS;
     867#endif /* VBOX_GUEST_PROP_TEST_NOTHREAD not defined */
     868
    821869    return rc;
    822870}
     
    10951143 */
    10961144/* static */
    1097 int Service::reqNotify(PFNHGCMSVCEXT pfnCallback, void *pvData,
    1098                        char *pszName, char *pszValue, uint32_t u32TimeHigh,
    1099                        uint32_t u32TimeLow, char *pszFlags)
     1145DECLCALLBACK(int) Service::reqNotify(PFNHGCMSVCEXT pfnCallback, void *pvData,
     1146                                     char *pszName, char *pszValue, uint32_t u32TimeHigh,
     1147                                     uint32_t u32TimeLow, char *pszFlags)
    11001148{
    11011149    LogFlowFunc (("pfnCallback=%p, pvData=%p, pszName=%p, pszValue=%p, u32TimeHigh=%u, u32TimeLow=%u, pszFlags=%p\n", pfnCallback, pvData, pszName, pszValue, u32TimeHigh, u32TimeLow, pszFlags));
     
    12471295                break;
    12481296
     1297            /* The host wishes to flush all pending notification */
     1298            case FLUSH_NOTIFICATIONS_HOST:
     1299                LogFlowFunc(("FLUSH_NOTIFICATIONS_HOST\n"));
     1300                if (cParms == 1)
     1301                {
     1302                    uint32_t cMsTimeout;
     1303                    rc = paParms[0].getUInt32(&cMsTimeout);
     1304                    if (RT_SUCCESS(rc))
     1305                        rc = flushNotifications(cMsTimeout);
     1306                }
     1307                else
     1308                    rc = VERR_INVALID_PARAMETER;
     1309                break;
     1310
    12491311            default:
    12501312                rc = VERR_NOT_SUPPORTED;
     
    12641326{
    12651327    int rc = VINF_SUCCESS;
    1266     unsigned count = 0;
    1267 
    1268     mfExitThread = true;
     1328
     1329    ASMAtomicWriteBool(&mfExitThread, true);
     1330
    12691331#ifndef VBOX_GUEST_PROP_TEST_NOTHREAD
     1332    /*
     1333     * Send a dummy request to the thread so it is forced out of the loop and
     1334     * notice that the exit flag is set.  Give up waiting after 5 mins.
     1335     * We call flushNotifications first to try clean up any pending request.
     1336     */
     1337    flushNotifications(120*1000);
     1338
    12701339    rc = RTReqCallEx(mReqQueue, NULL, 0, RTREQFLAGS_NO_WAIT, (PFNRT)reqVoid, 0);
    12711340    if (RT_SUCCESS(rc))
     1341    {
     1342        unsigned count = 0;
    12721343        do
    12731344        {
     
    12761347            Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
    12771348        } while ((VERR_TIMEOUT == rc) && (count < 300));
     1349    }
    12781350#endif /* VBOX_GUEST_PROP_TEST_NOTHREAD not defined */
    12791351    if (RT_SUCCESS(rc))
  • trunk/src/VBox/Main/ConsoleImpl.cpp

    r24664 r24703  
    105105#include <memory> // for auto_ptr
    106106#include <vector>
     107#include <typeinfo>
    107108
    108109
     
    13091310}
    13101311
     1312/**
     1313 * Helper that is used by powerDown to move the guest properties to VBoxSVC.
     1314 *
     1315 * @param   fSaving         Whether we're saving a machine state and should
     1316 *                          therefore save transient properties as well.
     1317 *
     1318 * @returns COM status code.
     1319 *
     1320 * @remarks This is called without holding the console lock.
     1321 */
     1322HRESULT Console::doMoveGuestPropertiesOnPowerOff(bool fSaving)
     1323{
     1324    /*
     1325     * First, flush any pending notifications.
     1326     */
     1327    VBOXHGCMSVCPARM parm[1];
     1328    parm[0].setUInt32(20*1000/*ms*/);
     1329    int vrc = mVMMDev->hgcmHostCall("VBoxGuestPropSvc", guestProp::FLUSH_NOTIFICATIONS_HOST, 1, &parm[0]);
     1330    if (RT_FAILURE(vrc))
     1331        LogRelFunc(("Flushing notifications failed with rc=%Rrc\n", vrc));
     1332
     1333    /*
     1334     * Enumerate the properties and
     1335     */
     1336    HRESULT                 hrc;
     1337    com::SafeArray<BSTR>    namesOut;
     1338    com::SafeArray<BSTR>    valuesOut;
     1339    com::SafeArray<ULONG64> timestampsOut;
     1340    com::SafeArray<BSTR>    flagsOut;
     1341    try
     1342    {
     1343        Bstr                pattern("");
     1344        hrc = doEnumerateGuestProperties(pattern, ComSafeArrayAsOutParam(namesOut),
     1345                                         ComSafeArrayAsOutParam(valuesOut),
     1346                                         ComSafeArrayAsOutParam(timestampsOut),
     1347                                         ComSafeArrayAsOutParam(flagsOut));
     1348        if (SUCCEEDED(hrc))
     1349        {
     1350            std::vector <BSTR>      names;
     1351            std::vector <BSTR>      values;
     1352            std::vector <ULONG64>   timestamps;
     1353            std::vector <BSTR>      flags;
     1354            for (size_t i = 0; i < namesOut.size(); ++i)
     1355            {
     1356                uint32_t fFlags = guestProp::NILFLAG;
     1357                vrc = guestProp::validateFlags(Utf8Str(flagsOut[i]).raw(), &fFlags); AssertRC(vrc);
     1358                if (   fSaving
     1359                    || !(fFlags & guestProp::TRANSIENT))
     1360                {
     1361                    names.push_back(namesOut[i]);
     1362                    values.push_back(valuesOut[i]);
     1363                    timestamps.push_back(timestampsOut[i]);
     1364                    flags.push_back(flagsOut[i]);
     1365                }
     1366            }
     1367            com::SafeArray<BSTR>    namesIn(names);
     1368            com::SafeArray<BSTR>    valuesIn(values);
     1369            com::SafeArray<ULONG64> timestampsIn(timestamps);
     1370            com::SafeArray<BSTR>    flagsIn(flags);
     1371            if (   namesIn.isNull()
     1372                || valuesIn.isNull()
     1373                || timestampsIn.isNull()
     1374                || flagsIn.isNull()
     1375               )
     1376                throw std::bad_alloc();
     1377            /* PushGuestProperties() calls DiscardSettings(), which calls us back */
     1378            mControl->PushGuestProperties(ComSafeArrayAsInParam(namesIn),
     1379                                          ComSafeArrayAsInParam(valuesIn),
     1380                                          ComSafeArrayAsInParam(timestampsIn),
     1381                                          ComSafeArrayAsInParam(flagsIn));
     1382        }
     1383    }
     1384    catch (...)
     1385    {
     1386        hrc = Console::handleUnexpectedExceptions(RT_SRC_POS);
     1387    }
     1388    if (FAILED(hrc))
     1389        LogRelFunc(("Failed with hrc=%Rhrc\n", hrc));
     1390    return hrc;
     1391}
     1392
     1393
     1394
    13111395#endif /* VBOX_WITH_GUEST_PROPS */
    13121396
     
    27042788/////////////////////////////////////////////////////////////////////////////
    27052789
     2790/**
     2791 * @copydoc VirtualBox::handleUnexpectedExceptions
     2792 */
     2793/* static */
     2794HRESULT Console::handleUnexpectedExceptions(RT_SRC_POS_DECL)
     2795{
     2796    try
     2797    {
     2798        /* re-throw the current exception */
     2799        throw;
     2800    }
     2801    catch (const std::exception &err)
     2802    {
     2803        return setError(E_FAIL, tr("Unexpected exception: %s [%s]\n%s[%d] (%s)"),
     2804                                err.what(), typeid(err).name(),
     2805                                pszFile, iLine, pszFunction);
     2806    }
     2807    catch (...)
     2808    {
     2809        return setError(E_FAIL, tr("Unknown exception\n%s[%d] (%s)"),
     2810                                pszFile, iLine, pszFunction);
     2811    }
     2812
     2813    /* should not get here */
     2814    AssertFailed();
     2815    return E_FAIL;
     2816}
    27062817
    27072818/* static */
     
    27502861    }
    27512862}
     2863
     2864// private methods
     2865/////////////////////////////////////////////////////////////////////////////
    27522866
    27532867/**
     
    48764990              || mMachineState == MachineState_Restoring
    48774991              || mMachineState == MachineState_TeleportingPausedVM
    4878               || mMachineState == MachineState_TeleportingIn         /** @todo Teleportation ???*/
     4992              || mMachineState == MachineState_TeleportingIn
    48794993              , ("Invalid machine state: %s\n", Global::stringifyMachineState(mMachineState)));
    48804994
     
    48945008        mVMPoweredOff = true;
    48955009
    4896     /* go to Stopping state if not already there. Note that we don't go from
    4897      * Saving/Restoring to Stopping because vmstateChangeCallback() needs it to
    4898      * set the state to Saved on VMSTATE_TERMINATED. In terms of protecting from
    4899      * inappropriate operations while leaving the lock below, Saving or
    4900      * Restoring should be fine too.  Ditto for Teleporting* -> Teleported. */
     5010    /*
     5011     * Go to Stopping state if not already there.
     5012     *
     5013     * Note that we don't go from Saving/Restoring to Stopping because
     5014     * vmstateChangeCallback() needs it to set the state to Saved on
     5015     * VMSTATE_TERMINATED. In terms of protecting from inappropriate operations
     5016     * while leaving the lock below, Saving or Restoring should be fine too.
     5017     * Ditto for TeleportingPausedVM -> Teleported.
     5018     */
    49015019    if (   mMachineState != MachineState_Saving
    49025020        && mMachineState != MachineState_Restoring
     
    49315049        aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
    49325050
    4933 #ifdef VBOX_WITH_HGCM
    4934 
    4935 # ifdef VBOX_WITH_GUEST_PROPS  /** @todo r=bird: This may be premature, the VM may still be running at this point! */
    4936 
    4937     /* Save all guest property store entries to the machine XML file */
    4938     com::SafeArray<BSTR> namesOut;
    4939     com::SafeArray<BSTR> valuesOut;
    4940     com::SafeArray<ULONG64> timestampsOut;
    4941     com::SafeArray<BSTR> flagsOut;
    4942     Bstr pattern("");
    4943     if (pattern.isNull()) /** @todo r=bird: What is pattern actually used for?  And, again, what's is the out-of-memory policy in main? */
    4944         rc = E_OUTOFMEMORY;
    4945     else
    4946         rc = doEnumerateGuestProperties(Bstr(""), ComSafeArrayAsOutParam(namesOut),
    4947                                         ComSafeArrayAsOutParam(valuesOut),
    4948                                         ComSafeArrayAsOutParam(timestampsOut),
    4949                                         ComSafeArrayAsOutParam(flagsOut));
    4950     if (SUCCEEDED(rc))
    4951     {
    4952         try
    4953         {
    4954             std::vector <BSTR> names;
    4955             std::vector <BSTR> values;
    4956             std::vector <ULONG64> timestamps;
    4957             std::vector <BSTR> flags;
    4958             for (unsigned i = 0; i < namesOut.size(); ++i)
    4959             {
    4960                 uint32_t fFlags;
    4961                 guestProp::validateFlags(Utf8Str(flagsOut[i]).raw(), &fFlags);
    4962                 if (   !(fFlags & guestProp::TRANSIENT)
    4963                     || mMachineState == MachineState_Saving
    4964                     || mMachineState == MachineState_LiveSnapshotting
    4965                   )
    4966                 {
    4967                     names.push_back(namesOut[i]);
    4968                     values.push_back(valuesOut[i]);
    4969                     timestamps.push_back(timestampsOut[i]);
    4970                     flags.push_back(flagsOut[i]);
    4971                 }
    4972             }
    4973             com::SafeArray<BSTR> namesIn(names);
    4974             com::SafeArray<BSTR> valuesIn(values);
    4975             com::SafeArray<ULONG64> timestampsIn(timestamps);
    4976             com::SafeArray<BSTR> flagsIn(flags);
    4977             if (   namesIn.isNull()
    4978                 || valuesIn.isNull()
    4979                 || timestampsIn.isNull()
    4980                 || flagsIn.isNull()
    4981                 )
    4982                 throw std::bad_alloc();
    4983             /* PushGuestProperties() calls DiscardSettings(), which calls us back */
    4984             alock.leave();
    4985             mControl->PushGuestProperties(ComSafeArrayAsInParam(namesIn),
    4986                                           ComSafeArrayAsInParam(valuesIn),
    4987                                           ComSafeArrayAsInParam(timestampsIn),
    4988                                           ComSafeArrayAsInParam(flagsIn));
    4989             alock.enter();
    4990         }
    4991         catch (std::bad_alloc)
    4992         {
    4993             rc = E_OUTOFMEMORY;
    4994         }
    4995     }
    4996 
    4997     /* advance percent count */
    4998     if (aProgress)
    4999         aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
    5000 
    5001 # endif /* VBOX_WITH_GUEST_PROPS defined */
    5002 
    5003     /* Shutdown HGCM services before stopping the guest, because they might
    5004      * need a cleanup. */
    5005     if (mVMMDev)
    5006     {
    5007         LogFlowThisFunc(("Shutdown HGCM...\n"));
    5008 
    5009         /* Leave the lock since EMT will call us back as addVMCaller() */
    5010         alock.leave();
    5011 
    5012         mVMMDev->hgcmShutdown();
    5013 
    5014         alock.enter();
    5015     }
    5016 
    5017     /* advance percent count */
    5018     if (aProgress)
    5019         aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
    5020 
    5021 #endif /* VBOX_WITH_HGCM */
    50225051
    50235052    /* ----------------------------------------------------------------------
     
    50525081    vrc = VINF_SUCCESS;
    50535082
    5054     /* Power off the VM if not already done that */
     5083    /*
     5084     * Power off the VM if not already done that.
     5085     * Leave the lock since EMT will call vmstateChangeCallback.
     5086     *
     5087     * Note that VMR3PowerOff() may fail here (invalid VMSTATE) if the
     5088     * VM-(guest-)initiated power off happened in parallel a ms before this
     5089     * call. So far, we let this error pop up on the user's side.
     5090     */
    50555091    if (!mVMPoweredOff)
    50565092    {
    50575093        LogFlowThisFunc(("Powering off the VM...\n"));
    5058 
    5059         /* Leave the lock since EMT will call us back on VMR3PowerOff() */
    50605094        alock.leave();
    5061 
    50625095        vrc = VMR3PowerOff(mpVM);
    5063 
    5064         /* Note that VMR3PowerOff() may fail here (invalid VMSTATE) if the
    5065          * VM-(guest-)initiated power off happened in parallel a ms before this
    5066          * call. So far, we let this error pop up on the user's side. */
    5067 
    50685096        alock.enter();
    5069 
    50705097    }
    50715098    else
    50725099    {
    5073         /* reset the flag for further re-use */
     5100        /** @todo r=bird: Doesn't make sense. Please remove after 3.1 has been branched
     5101         *        off. */
     5102        /* reset the flag for future re-use */
    50745103        mVMPoweredOff = false;
    50755104    }
     
    50795108        aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
    50805109
     5110#ifdef VBOX_WITH_HGCM
     5111# ifdef VBOX_WITH_GUEST_PROPS
     5112    /*
     5113     * Save all guest property store entries to the machine XML file
     5114     * and hand controll over to VBoxSVC.  Ignoring failure for now.
     5115     */
     5116    LogFlowThisFunc(("Moving Guest Properties to XML/VBoxSVC...\n"));
     5117    bool fIsSaving = mMachineState == MachineState_Saving
     5118                  || mMachineState == MachineState_LiveSnapshotting;
     5119    alock.leave();
     5120    doMoveGuestPropertiesOnPowerOff(fIsSaving);
     5121    alock.enter();
     5122
     5123    /* advance percent count */
     5124    if (aProgress)
     5125        aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
     5126
     5127# endif /* VBOX_WITH_GUEST_PROPS defined */
     5128
     5129    /* Shutdown HGCM services before destroying the VM. */
     5130    if (mVMMDev)
     5131    {
     5132        LogFlowThisFunc(("Shutdown HGCM...\n"));
     5133
     5134        /* Leave the lock since EMT will call us back as addVMCaller() */
     5135        alock.leave();
     5136
     5137        mVMMDev->hgcmShutdown();
     5138
     5139        alock.enter();
     5140    }
     5141
     5142    /* advance percent count */
     5143    if (aProgress)
     5144        aProgress->SetCurrentOperationProgress(99 * (++ step) / StepCount );
     5145
     5146#endif /* VBOX_WITH_HGCM */
     5147
    50815148    LogFlowThisFunc(("Ready for VM destruction.\n"));
    50825149
     
    50855152    if (RT_SUCCESS(vrc) || autoCaller.state() == InUninit)
    50865153    {
    5087         /* If the machine has an USB comtroller, release all USB devices
     5154        /* If the machine has an USB controller, release all USB devices
    50885155         * (symmetric to the code in captureUSBDevices()) */
    50895156        bool fHasUSBController = false;
  • trunk/src/VBox/Main/include/ConsoleImpl.h

    r24579 r24703  
    223223    }
    224224
     225    static HRESULT handleUnexpectedExceptions(RT_SRC_POS_DECL);
     226
    225227    static const char *convertControllerTypeToDev(StorageControllerType_T enmCtrlType);
    226228    static HRESULT convertBusPortDeviceToLun(StorageBus_T enmBus, LONG port, LONG device, unsigned &uLun);
     
    505507
    506508#ifdef VBOX_WITH_GUEST_PROPS
    507     static DECLCALLBACK(int)    doGuestPropNotification (void *pvExtension, uint32_t,
    508                                                          void *pvParms, uint32_t cbParms);
    509     HRESULT doEnumerateGuestProperties (CBSTR aPatterns,
    510                                         ComSafeArrayOut(BSTR, aNames),
    511                                         ComSafeArrayOut(BSTR, aValues),
    512                                         ComSafeArrayOut(ULONG64, aTimestamps),
    513                                         ComSafeArrayOut(BSTR, aFlags));
     509    static DECLCALLBACK(int)    doGuestPropNotification(void *pvExtension, uint32_t, void *pvParms, uint32_t cbParms);
     510    HRESULT                     doMoveGuestPropertiesOnPowerOff(bool fSaving);
     511    HRESULT                     doEnumerateGuestProperties(CBSTR aPatterns,
     512                                                           ComSafeArrayOut(BSTR, aNames),
     513                                                           ComSafeArrayOut(BSTR, aValues),
     514                                                           ComSafeArrayOut(ULONG64, aTimestamps),
     515                                                           ComSafeArrayOut(BSTR, aFlags));
    514516
    515517    bool enabledGuestPropertiesVRDP (void);
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