VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/RecordingSettingsImpl.cpp@ 93412

Last change on this file since 93412 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.9 KB
Line 
1/* $Id: RecordingSettingsImpl.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 *
4 * VirtualBox COM class implementation - Machine capture settings.
5 */
6
7/*
8 * Copyright (C) 2018-2022 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#define LOG_GROUP LOG_GROUP_MAIN_RECORDINGSETTINGS
20#include "LoggingNew.h"
21
22#include "RecordingSettingsImpl.h"
23#include "RecordingScreenSettingsImpl.h"
24#include "MachineImpl.h"
25
26#include <iprt/cpp/utils.h>
27#include <VBox/settings.h>
28
29#include "AutoStateDep.h"
30#include "AutoCaller.h"
31#include "Global.h"
32
33////////////////////////////////////////////////////////////////////////////////
34//
35// RecordSettings private data definition
36//
37////////////////////////////////////////////////////////////////////////////////
38
39struct RecordingSettings::Data
40{
41 Data()
42 : pMachine(NULL)
43 { }
44
45 Machine * const pMachine;
46 const ComObjPtr<RecordingSettings> pPeer;
47 RecordScreenSettingsMap mapScreenObj;
48
49 // use the XML settings structure in the members for simplicity
50 Backupable<settings::RecordingSettings> bd;
51};
52
53DEFINE_EMPTY_CTOR_DTOR(RecordingSettings)
54
55HRESULT RecordingSettings::FinalConstruct()
56{
57 return BaseFinalConstruct();
58}
59
60void RecordingSettings::FinalRelease()
61{
62 uninit();
63 BaseFinalRelease();
64}
65
66/**
67 * Initializes the recording settings object.
68 *
69 * @returns COM result indicator
70 */
71HRESULT RecordingSettings::init(Machine *aParent)
72{
73 LogFlowThisFuncEnter();
74 LogFlowThisFunc(("aParent: %p\n", aParent));
75
76 ComAssertRet(aParent, E_INVALIDARG);
77
78 /* Enclose the state transition NotReady->InInit->Ready */
79 AutoInitSpan autoInitSpan(this);
80 AssertReturn(autoInitSpan.isOk(), E_FAIL);
81
82 m = new Data();
83
84 /* share the parent weakly */
85 unconst(m->pMachine) = aParent;
86
87 m->bd.allocate();
88
89 autoInitSpan.setSucceeded();
90
91 LogFlowThisFuncLeave();
92 return S_OK;
93}
94
95/**
96 * Initializes the capture settings object given another capture settings object
97 * (a kind of copy constructor). This object shares data with
98 * the object passed as an argument.
99 *
100 * @note This object must be destroyed before the original object
101 * it shares data with is destroyed.
102 *
103 * @note Locks @a aThat object for reading.
104 */
105HRESULT RecordingSettings::init(Machine *aParent, RecordingSettings *aThat)
106{
107 LogFlowThisFuncEnter();
108 LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
109
110 ComAssertRet(aParent && aThat, E_INVALIDARG);
111
112 /* Enclose the state transition NotReady->InInit->Ready */
113 AutoInitSpan autoInitSpan(this);
114 AssertReturn(autoInitSpan.isOk(), E_FAIL);
115
116 m = new Data();
117
118 unconst(m->pMachine) = aParent;
119 unconst(m->pPeer) = aThat;
120
121 AutoCaller thatCaller(aThat);
122 AssertComRCReturnRC(thatCaller.rc());
123
124 AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
125
126 m->bd.share(aThat->m->bd);
127 m->mapScreenObj = aThat->m->mapScreenObj;
128
129 autoInitSpan.setSucceeded();
130
131 LogFlowThisFuncLeave();
132 return S_OK;
133}
134
135/**
136 * Initializes the guest object given another guest object
137 * (a kind of copy constructor). This object makes a private copy of data
138 * of the original object passed as an argument.
139 *
140 * @note Locks @a aThat object for reading.
141 */
142HRESULT RecordingSettings::initCopy(Machine *aParent, RecordingSettings *aThat)
143{
144 LogFlowThisFuncEnter();
145 LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
146
147 ComAssertRet(aParent && aThat, E_INVALIDARG);
148
149 /* Enclose the state transition NotReady->InInit->Ready */
150 AutoInitSpan autoInitSpan(this);
151 AssertReturn(autoInitSpan.isOk(), E_FAIL);
152
153 m = new Data();
154
155 unconst(m->pMachine) = aParent;
156 // mPeer is left null
157
158 AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
159
160 m->bd.attachCopy(aThat->m->bd);
161 m->mapScreenObj = aThat->m->mapScreenObj;
162
163 autoInitSpan.setSucceeded();
164
165 LogFlowThisFuncLeave();
166 return S_OK;
167}
168
169/**
170 * Uninitializes the instance and sets the ready flag to FALSE.
171 * Called either from FinalRelease() or by the parent when it gets destroyed.
172 */
173void RecordingSettings::uninit()
174{
175 LogFlowThisFuncEnter();
176
177 /* Enclose the state transition Ready->InUninit->NotReady */
178 AutoUninitSpan autoUninitSpan(this);
179 if (autoUninitSpan.uninitDone())
180 return;
181
182 /* Note: Do *not* call i_reset() here, as the shared recording configuration
183 * otherwise gets destructed when this object goes out of scope or is destroyed. */
184
185 m->bd.free();
186
187 unconst(m->pPeer) = NULL;
188 unconst(m->pMachine) = NULL;
189
190 delete m;
191 m = NULL;
192
193 LogFlowThisFuncLeave();
194}
195
196// IRecordSettings properties
197/////////////////////////////////////////////////////////////////////////////
198
199HRESULT RecordingSettings::getEnabled(BOOL *enabled)
200{
201 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
202
203 *enabled = m->bd->fEnabled;
204
205 return S_OK;
206}
207
208HRESULT RecordingSettings::setEnabled(BOOL enable)
209{
210 /* the machine needs to be mutable */
211 AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
212 if (FAILED(adep.rc())) return adep.rc();
213
214 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
215
216 const bool fEnabled = RT_BOOL(enable);
217
218 HRESULT rc = S_OK;
219
220 if (m->bd->fEnabled != fEnabled)
221 {
222 m->bd.backup();
223 m->bd->fEnabled = fEnabled;
224
225 alock.release();
226
227 rc = m->pMachine->i_onRecordingChange(enable);
228 if (FAILED(rc))
229 {
230 /*
231 * Normally we would do the actual change _after_ i_onCaptureChange() succeeded.
232 * We cannot do this because that function uses RecordSettings::GetEnabled to
233 * determine if it should start or stop capturing. Therefore we need to manually
234 * undo change.
235 */
236 alock.acquire();
237 m->bd->fEnabled = m->bd.backedUpData()->fEnabled;
238 }
239 else
240 {
241 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // pMachine is const, needs no locking
242 m->pMachine->i_setModified(Machine::IsModified_Recording);
243
244 /* Make sure to release the mutable dependency lock from above before
245 * actually saving the settings. */
246 adep.release();
247
248 /** Save settings if online - @todo why is this required? -- @bugref{6818} */
249 if (Global::IsOnline(m->pMachine->i_getMachineState()))
250 rc = m->pMachine->i_saveSettings(NULL, mlock);
251 }
252 }
253
254 return rc;
255}
256
257HRESULT RecordingSettings::getScreens(std::vector<ComPtr<IRecordingScreenSettings> > &aRecordScreenSettings)
258{
259 LogFlowThisFuncEnter();
260
261 AssertPtr(m->pMachine);
262 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
263 m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
264 ULONG cMonitors = 0;
265 if (!pGraphicsAdapter.isNull())
266 pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
267
268 i_syncToMachineDisplays(cMonitors);
269
270 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
271
272 aRecordScreenSettings.clear();
273 aRecordScreenSettings.resize(m->mapScreenObj.size());
274
275 RecordScreenSettingsMap::const_iterator itScreenSettings = m->mapScreenObj.begin();
276 size_t i = 0;
277 while (itScreenSettings != m->mapScreenObj.end())
278 {
279 itScreenSettings->second.queryInterfaceTo(aRecordScreenSettings[i].asOutParam());
280 Assert(aRecordScreenSettings[i].isNotNull());
281 ++i;
282 ++itScreenSettings;
283 }
284
285 Assert(aRecordScreenSettings.size() == m->mapScreenObj.size());
286
287 return S_OK;
288}
289
290HRESULT RecordingSettings::getScreenSettings(ULONG uScreenId, ComPtr<IRecordingScreenSettings> &aRecordScreenSettings)
291{
292 LogFlowThisFuncEnter();
293
294 AssertPtr(m->pMachine);
295 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
296 m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
297 ULONG cMonitors = 0;
298 if (!pGraphicsAdapter.isNull())
299 pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
300
301 i_syncToMachineDisplays(cMonitors);
302
303 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
304
305 if (uScreenId + 1 > m->mapScreenObj.size())
306 return setError(E_INVALIDARG, tr("Invalid screen ID specified"));
307
308 RecordScreenSettingsMap::const_iterator itScreenSettings = m->mapScreenObj.find(uScreenId);
309 if (itScreenSettings != m->mapScreenObj.end())
310 {
311 itScreenSettings->second.queryInterfaceTo(aRecordScreenSettings.asOutParam());
312 return S_OK;
313 }
314
315 return VBOX_E_OBJECT_NOT_FOUND;
316}
317
318// IRecordSettings methods
319/////////////////////////////////////////////////////////////////////////////
320
321// public methods only for internal purposes
322/////////////////////////////////////////////////////////////////////////////
323
324/**
325 * Adds a screen settings object to a particular map.
326 *
327 * @returns IPRT status code. VERR_ALREADY_EXISTS if the object in question already exists.
328 * @param screenSettingsMap Map to add screen settings to.
329 * @param uScreenId Screen ID to add settings for.
330 * @param data Recording screen settings to use for that screen.
331 */
332int RecordingSettings::i_createScreenObj(RecordScreenSettingsMap &screenSettingsMap,
333 uint32_t uScreenId, const settings::RecordingScreenSettings &data)
334{
335 LogFlowThisFunc(("Screen %RU32\n", uScreenId));
336
337 if (screenSettingsMap.find(uScreenId) != screenSettingsMap.end())
338 {
339 AssertFailed();
340 return VERR_ALREADY_EXISTS;
341 }
342
343 int vrc = VINF_SUCCESS;
344
345 ComObjPtr<RecordingScreenSettings> recordingScreenSettings;
346 HRESULT rc = recordingScreenSettings.createObject();
347 if (SUCCEEDED(rc))
348 {
349 rc = recordingScreenSettings->init(this, uScreenId, data);
350 if (SUCCEEDED(rc))
351 {
352 try
353 {
354 screenSettingsMap[uScreenId] = recordingScreenSettings;
355 }
356 catch (std::bad_alloc &)
357 {
358 vrc = VERR_NO_MEMORY;
359 }
360 }
361 }
362
363 return vrc;
364}
365
366/**
367 * Removes a screen settings object from a particular map.
368 *
369 * @returns IPRT status code. VERR_NOT_FOUND if specified screen was not found.
370 * @param screenSettingsMap Map to remove screen settings from.
371 * @param uScreenId ID of screen to remove.
372 */
373int RecordingSettings::i_destroyScreenObj(RecordScreenSettingsMap &screenSettingsMap, uint32_t uScreenId)
374{
375 LogFlowThisFunc(("Screen %RU32\n", uScreenId));
376
377 AssertReturn(uScreenId > 0, VERR_INVALID_PARAMETER); /* Removing screen 0 isn't a good idea. */
378
379 RecordScreenSettingsMap::iterator itScreen = screenSettingsMap.find(uScreenId);
380 if (itScreen == screenSettingsMap.end())
381 {
382 AssertFailed();
383 return VERR_NOT_FOUND;
384 }
385
386 /* Make sure to consume the pointer before the one of the
387 * iterator gets released. */
388 ComObjPtr<RecordingScreenSettings> pScreenSettings = itScreen->second;
389
390 screenSettingsMap.erase(itScreen);
391
392 pScreenSettings.setNull();
393
394 return VINF_SUCCESS;
395}
396
397/**
398 * Destroys all screen settings objects of a particular map.
399 *
400 * @returns IPRT status code.
401 * @param screenSettingsMap Map to destroy screen settings objects for.
402 */
403int RecordingSettings::i_destroyAllScreenObj(RecordScreenSettingsMap &screenSettingsMap)
404{
405 LogFlowThisFuncEnter();
406
407 RecordScreenSettingsMap::iterator itScreen = screenSettingsMap.begin();
408 while (itScreen != screenSettingsMap.end())
409 {
410 /* Make sure to consume the pointer before the one of the
411 * iterator gets released. */
412 ComObjPtr<RecordingScreenSettings> pScreenSettings = itScreen->second;
413
414 screenSettingsMap.erase(itScreen);
415
416 pScreenSettings.setNull();
417
418 itScreen = screenSettingsMap.begin();
419 }
420
421 Assert(screenSettingsMap.size() == 0);
422 return VINF_SUCCESS;
423}
424
425/**
426 * Loads settings from the given settings.
427 * May be called once right after this object creation.
428 *
429 * @param data Capture settings to load from.
430 *
431 * @note Locks this object for writing.
432 */
433HRESULT RecordingSettings::i_loadSettings(const settings::RecordingSettings &data)
434{
435 LogFlowThisFuncEnter();
436
437 AutoCaller autoCaller(this);
438 AssertComRCReturnRC(autoCaller.rc());
439
440 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
441
442 HRESULT rc = S_OK;
443
444 i_reset();
445
446 LogFlowThisFunc(("Data has %zu screens\n", data.mapScreens.size()));
447
448 settings::RecordingScreenMap::const_iterator itScreen = data.mapScreens.begin();
449 while (itScreen != data.mapScreens.end())
450 {
451 int vrc = i_createScreenObj(m->mapScreenObj,
452 itScreen->first /* uScreenId */, itScreen->second /* Settings */);
453 if (RT_FAILURE(vrc))
454 {
455 rc = E_OUTOFMEMORY;
456 break;
457 }
458
459 ++itScreen;
460 }
461
462 if (FAILED(rc))
463 return rc;
464
465 ComAssertComRC(rc);
466 Assert(m->mapScreenObj.size() == data.mapScreens.size());
467
468 // simply copy
469 m->bd.assignCopy(&data);
470
471 LogFlowThisFunc(("Returning %Rhrc\n", rc));
472 return rc;
473}
474
475/**
476 * Resets the internal object state by destroying all screen settings objects.
477 */
478void RecordingSettings::i_reset(void)
479{
480 LogFlowThisFuncEnter();
481
482 i_destroyAllScreenObj(m->mapScreenObj);
483 m->bd->mapScreens.clear();
484}
485
486/**
487 * Saves settings to the given settings.
488 *
489 * @param data Where to store the capture settings to.
490 *
491 * @note Locks this object for reading.
492 */
493HRESULT RecordingSettings::i_saveSettings(settings::RecordingSettings &data)
494{
495 LogFlowThisFuncEnter();
496
497 AutoCaller autoCaller(this);
498 AssertComRCReturnRC(autoCaller.rc());
499
500 AssertPtr(m->pMachine);
501 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
502 m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
503 ULONG cMonitors = 0;
504 if (!pGraphicsAdapter.isNull())
505 pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
506
507 int rc2 = i_syncToMachineDisplays(cMonitors);
508 AssertRC(rc2);
509
510 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
511
512 data = *m->bd.data();
513
514 settings::RecordingScreenMap::iterator itScreen = data.mapScreens.begin();
515 while (itScreen != data.mapScreens.end())
516 {
517 /* Store relative path of capture file if possible. */
518 m->pMachine->i_copyPathRelativeToMachine(itScreen->second.File.strName /* Source */,
519 itScreen->second.File.strName /* Target */);
520 ++itScreen;
521 }
522
523 LogFlowThisFuncLeave();
524 return S_OK;
525}
526
527void RecordingSettings::i_rollback()
528{
529 /* sanity */
530 AutoCaller autoCaller(this);
531 AssertComRCReturnVoid(autoCaller.rc());
532
533 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
534 m->bd.rollback();
535}
536
537void RecordingSettings::i_commit()
538{
539 /* sanity */
540 AutoCaller autoCaller(this);
541 AssertComRCReturnVoid(autoCaller.rc());
542
543 /* sanity too */
544 AutoCaller peerCaller(m->pPeer);
545 AssertComRCReturnVoid(peerCaller.rc());
546
547 /* lock both for writing since we modify both (mPeer is "master" so locked
548 * first) */
549 AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
550
551 if (m->bd.isBackedUp())
552 {
553 m->bd.commit();
554 if (m->pPeer)
555 {
556 /* attach new data to the peer and reshare it */
557 m->pPeer->m->bd.attach(m->bd);
558 }
559 }
560}
561
562void RecordingSettings::i_copyFrom(RecordingSettings *aThat)
563{
564 AssertReturnVoid(aThat != NULL);
565
566 /* sanity */
567 AutoCaller autoCaller(this);
568 AssertComRCReturnVoid(autoCaller.rc());
569
570 /* sanity too */
571 AutoCaller thatCaller(aThat);
572 AssertComRCReturnVoid(thatCaller.rc());
573
574 /* peer is not modified, lock it for reading (aThat is "master" so locked
575 * first) */
576 AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
577 AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
578
579 /* this will back up current data */
580 m->bd.assignCopy(aThat->m->bd);
581}
582
583void RecordingSettings::i_applyDefaults(void)
584{
585 /* sanity */
586 AutoCaller autoCaller(this);
587 AssertComRCReturnVoid(autoCaller.rc());
588
589 AssertPtr(m->pMachine);
590 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
591 m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
592 ULONG cMonitors = 0;
593 if (!pGraphicsAdapter.isNull())
594 pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
595
596 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
597
598 /* Initialize default capturing settings here. */
599 m->bd->fEnabled = false;
600
601 /* First, do a reset so that all internal screen settings objects are destroyed. */
602 i_reset();
603 /* Second, sync (again) to configured machine displays to (re-)create screen settings objects. */
604 i_syncToMachineDisplays(cMonitors);
605}
606
607/**
608 * Returns the full path to the default video capture file.
609 */
610int RecordingSettings::i_getDefaultFilename(Utf8Str &strFile, bool fWithFileExtension)
611{
612 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
613
614 strFile = m->pMachine->i_getSettingsFileFull(); // path/to/machinesfolder/vmname/vmname.vbox
615 strFile.stripSuffix(); // path/to/machinesfolder/vmname/vmname
616 if (fWithFileExtension)
617 strFile.append(".webm"); // path/to/machinesfolder/vmname/vmname.webm
618
619 return VINF_SUCCESS;
620}
621
622/**
623 * Determines whether the recording settings currently can be changed or not.
624 *
625 * @returns \c true if the settings can be changed, \c false if not.
626 */
627bool RecordingSettings::i_canChangeSettings(void)
628{
629 AutoAnyStateDependency adep(m->pMachine);
630 if (FAILED(adep.rc()))
631 return false;
632
633 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
634
635 /* Only allow settings to be changed when recording is disabled when the machine is running. */
636 if ( Global::IsOnline(adep.machineState())
637 && m->bd->fEnabled)
638 {
639 return false;
640 }
641
642 return true;
643}
644
645/**
646 * Gets called when the machine object needs to know that the recording settings
647 * have been changed.
648 */
649void RecordingSettings::i_onSettingsChanged(void)
650{
651 LogFlowThisFuncEnter();
652
653 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
654 m->pMachine->i_setModified(Machine::IsModified_Recording);
655 mlock.release();
656
657 LogFlowThisFuncLeave();
658}
659
660/**
661 * Synchronizes the screen settings (COM) objects and configuration data
662 * to the number of the machine's configured displays.
663 */
664int RecordingSettings::i_syncToMachineDisplays(uint32_t cMonitors)
665{
666 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
667
668 LogFlowThisFunc(("cMonitors=%RU32\n", cMonitors));
669 LogFlowThisFunc(("Data screen count = %zu, COM object count = %zu\n", m->bd->mapScreens.size(), m->mapScreenObj.size()));
670
671 /* If counts match, take a shortcut. */
672 if (cMonitors == m->mapScreenObj.size())
673 return VINF_SUCCESS;
674
675 /* Create all new screen settings objects which are not there yet. */
676 for (ULONG i = 0; i < cMonitors; i++)
677 {
678 if (m->mapScreenObj.find(i) == m->mapScreenObj.end())
679 {
680 settings::RecordingScreenMap::const_iterator itScreen = m->bd->mapScreens.find(i);
681 if (itScreen == m->bd->mapScreens.end())
682 {
683 settings::RecordingScreenSettings defaultScreenSettings; /* Apply default settings. */
684 m->bd->mapScreens[i] = defaultScreenSettings;
685 }
686
687 int vrc2 = i_createScreenObj(m->mapScreenObj, i /* Screen ID */, m->bd->mapScreens[i]);
688 AssertRC(vrc2);
689 }
690 }
691
692 /* Remove all left over screen settings objects which are not needed anymore. */
693 const ULONG cSettings = (ULONG)m->mapScreenObj.size();
694 for (ULONG i = cMonitors; i < cSettings; i++)
695 {
696 m->bd->mapScreens.erase(i);
697 int vrc2 = i_destroyScreenObj(m->mapScreenObj, i /* Screen ID */);
698 AssertRC(vrc2);
699 }
700
701 Assert(m->mapScreenObj.size() == cMonitors);
702 Assert(m->bd->mapScreens.size() == cMonitors);
703
704 LogFlowThisFuncLeave();
705 return VINF_SUCCESS;
706}
707
Note: See TracBrowser for help on using the repository browser.

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