VirtualBox

source: vbox/trunk/src/VBox/Main/VFSExplorerImpl.cpp@ 25414

Last change on this file since 25414 was 25310, checked in by vboxsync, 14 years ago

Main: lock validator, first batch: implement per-thread stack to trace locking (disabled by default, use VBOX_WITH_LOCK_VALIDATOR, but that WILL FAIL presently)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.5 KB
Line 
1/* $Id: VFSExplorerImpl.cpp 25310 2009-12-10 17:06:44Z vboxsync $ */
2/** @file
3 *
4 * IVFSExplorer COM class implementations.
5 */
6
7/*
8 * Copyright (C) 2009 Sun Microsystems, Inc.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22
23#include <iprt/dir.h>
24#include <iprt/path.h>
25#include <iprt/file.h>
26#include <iprt/s3.h>
27
28#include <VBox/com/array.h>
29
30#include <VBox/param.h>
31#include <VBox/version.h>
32
33#include "VFSExplorerImpl.h"
34#include "VirtualBoxImpl.h"
35#include "ProgressImpl.h"
36
37#include "Logging.h"
38
39////////////////////////////////////////////////////////////////////////////////
40//
41// VFSExplorer definitions
42//
43////////////////////////////////////////////////////////////////////////////////
44
45/* opaque private instance data of VFSExplorer class */
46struct VFSExplorer::Data
47{
48 struct DirEntry
49 {
50 DirEntry(Utf8Str aName, VFSFileType_T aType)
51 : name(aName)
52 , type(aType) {}
53
54 Utf8Str name;
55 VFSFileType_T type;
56 };
57
58 VFSType_T storageType;
59 Utf8Str strUsername;
60 Utf8Str strPassword;
61 Utf8Str strHostname;
62 Utf8Str strPath;
63 Utf8Str strBucket;
64
65 std::list<DirEntry> entryList;
66};
67
68DEFINE_EMPTY_CTOR_DTOR(VFSExplorer)
69
70/**
71 * VFSExplorer COM initializer.
72 * @param
73 * @return
74 */
75HRESULT VFSExplorer::init(VFSType_T aType, Utf8Str aFilePath, Utf8Str aHostname, Utf8Str aUsername, Utf8Str aPassword, VirtualBox *aVirtualBox)
76{
77 /* Enclose the state transition NotReady->InInit->Ready */
78 AutoInitSpan autoInitSpan(this);
79 AssertReturn(autoInitSpan.isOk(), E_FAIL);
80
81 /* Weak reference to a VirtualBox object */
82 unconst(mVirtualBox) = aVirtualBox;
83
84 /* initialize data */
85 m = new Data;
86
87 m->storageType = aType;
88 m->strPath = aFilePath;
89 m->strHostname = aHostname;
90 m->strUsername = aUsername;
91 m->strPassword = aPassword;
92
93 if (m->storageType == VFSType_S3)
94 {
95 size_t bpos = aFilePath.find("/", 1);
96 if (bpos != Utf8Str::npos)
97 {
98 m->strBucket = aFilePath.substr(1, bpos - 1); /* The bucket without any slashes */
99 aFilePath = aFilePath.substr(bpos); /* The rest of the file path */
100 }
101 }
102
103 /* Confirm a successful initialization */
104 autoInitSpan.setSucceeded();
105
106 return S_OK;
107}
108
109/**
110 * VFSExplorer COM uninitializer.
111 * @return
112 */
113void VFSExplorer::uninit()
114{
115 delete m;
116 m = NULL;
117}
118
119/**
120 * Public method implementation.
121 * @param
122 * @return
123 */
124STDMETHODIMP VFSExplorer::COMGETTER(Path)(BSTR *aPath)
125{
126 if (!aPath)
127 return E_POINTER;
128
129 AutoCaller autoCaller(this);
130 if (FAILED(autoCaller.rc())) return autoCaller.rc();
131
132 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
133
134 Bstr bstrPath(m->strPath);
135 bstrPath.cloneTo(aPath);
136
137 return S_OK;
138}
139
140STDMETHODIMP VFSExplorer::COMGETTER(Type)(VFSType_T *aType)
141{
142 if (!aType)
143 return E_POINTER;
144
145 AutoCaller autoCaller(this);
146 if (FAILED(autoCaller.rc())) return autoCaller.rc();
147
148 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
149
150 *aType = m->storageType;
151
152 return S_OK;
153}
154
155struct VFSExplorer::TaskVFSExplorer
156{
157 enum TaskType
158 {
159 Update,
160 Delete
161 };
162
163 TaskVFSExplorer(TaskType aTaskType, VFSExplorer *aThat, Progress *aProgress)
164 : taskType(aTaskType),
165 pVFSExplorer(aThat),
166 progress(aProgress),
167 rc(S_OK)
168 {}
169 ~TaskVFSExplorer() {}
170
171 int startThread();
172 static int taskThread(RTTHREAD aThread, void *pvUser);
173 static int uploadProgress(unsigned uPercent, void *pvUser);
174
175 TaskType taskType;
176 VFSExplorer *pVFSExplorer;
177 ComObjPtr<Progress> progress;
178 HRESULT rc;
179
180 /* task data */
181 std::list<Utf8Str> filenames;
182};
183
184int VFSExplorer::TaskVFSExplorer::startThread()
185{
186 int vrc = RTThreadCreate(NULL, VFSExplorer::TaskVFSExplorer::taskThread, this,
187 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
188 "Explorer::Task");
189
190 ComAssertMsgRCRet(vrc,
191 ("Could not create taskThreadVFS (%Rrc)\n", vrc), E_FAIL);
192
193 return vrc;
194}
195
196/* static */
197DECLCALLBACK(int) VFSExplorer::TaskVFSExplorer::taskThread(RTTHREAD /* aThread */, void *pvUser)
198{
199 std::auto_ptr<TaskVFSExplorer> task(static_cast<TaskVFSExplorer*>(pvUser));
200 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
201
202 VFSExplorer *pVFSExplorer = task->pVFSExplorer;
203
204 LogFlowFuncEnter();
205 LogFlowFunc(("VFSExplorer %p\n", pVFSExplorer));
206
207 HRESULT rc = S_OK;
208
209 switch(task->taskType)
210 {
211 case TaskVFSExplorer::Update:
212 {
213 if (pVFSExplorer->m->storageType == VFSType_File)
214 rc = pVFSExplorer->updateFS(task.get());
215 else if (pVFSExplorer->m->storageType == VFSType_S3)
216 rc = pVFSExplorer->updateS3(task.get());
217 break;
218 }
219 case TaskVFSExplorer::Delete:
220 {
221 if (pVFSExplorer->m->storageType == VFSType_File)
222 rc = pVFSExplorer->deleteFS(task.get());
223 else if (pVFSExplorer->m->storageType == VFSType_S3)
224 rc = pVFSExplorer->deleteS3(task.get());
225 break;
226 }
227 }
228
229 LogFlowFunc(("rc=%Rhrc\n", rc));
230 LogFlowFuncLeave();
231
232 return VINF_SUCCESS;
233}
234
235/* static */
236int VFSExplorer::TaskVFSExplorer::uploadProgress(unsigned uPercent, void *pvUser)
237{
238 VFSExplorer::TaskVFSExplorer* pTask = *(VFSExplorer::TaskVFSExplorer**)pvUser;
239
240 if (pTask &&
241 !pTask->progress.isNull())
242 {
243 BOOL fCanceled;
244 pTask->progress->COMGETTER(Canceled)(&fCanceled);
245 if (fCanceled)
246 return -1;
247 pTask->progress->SetCurrentOperationProgress(uPercent);
248 }
249 return VINF_SUCCESS;
250}
251
252VFSFileType_T VFSExplorer::RTToVFSFileType(int aType) const
253{
254 VFSFileType_T t;
255 switch(aType)
256 {
257 default:
258 case RTDIRENTRYTYPE_UNKNOWN: t = VFSFileType_Unknown; break;
259 case RTDIRENTRYTYPE_FIFO: t = VFSFileType_Fifo; break;
260 case RTDIRENTRYTYPE_DEV_CHAR: t = VFSFileType_DevChar; break;
261 case RTDIRENTRYTYPE_DIRECTORY: t = VFSFileType_Directory; break;
262 case RTDIRENTRYTYPE_DEV_BLOCK: t = VFSFileType_DevBlock; break;
263 case RTDIRENTRYTYPE_FILE: t = VFSFileType_File; break;
264 case RTDIRENTRYTYPE_SYMLINK: t = VFSFileType_SymLink; break;
265 case RTDIRENTRYTYPE_SOCKET: t = VFSFileType_Socket; break;
266 case RTDIRENTRYTYPE_WHITEOUT: t = VFSFileType_WhiteOut; break;
267 }
268 return t;
269}
270
271HRESULT VFSExplorer::updateFS(TaskVFSExplorer *aTask)
272{
273 LogFlowFuncEnter();
274
275 AutoCaller autoCaller(this);
276 if (FAILED(autoCaller.rc())) return autoCaller.rc();
277
278 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
279
280 HRESULT rc = S_OK;
281
282 std::list<VFSExplorer::Data::DirEntry> fileList;
283 char *pszPath = NULL;
284 PRTDIR pDir = NULL;
285 try
286 {
287 pszPath = RTStrDup(m->strPath.c_str());
288 RTPathStripFilename(pszPath);
289 int vrc = RTDirOpen(&pDir, pszPath);
290 if (RT_FAILURE(vrc))
291 throw setError(VBOX_E_FILE_ERROR, tr ("Can't open directory '%s' (%Rrc)"), pszPath, vrc);
292
293 if(aTask->progress)
294 aTask->progress->SetCurrentOperationProgress(33);
295 RTDIRENTRY entry;
296 while (RT_SUCCESS(vrc))
297 {
298 vrc = RTDirRead(pDir, &entry, NULL);
299 if (RT_SUCCESS(vrc))
300 {
301 Utf8Str name(entry.szName);
302 if (name != "." &&
303 name != "..")
304 fileList.push_back(VFSExplorer::Data::DirEntry(name, RTToVFSFileType(entry.enmType)));
305 }
306 }
307 if(aTask->progress)
308 aTask->progress->SetCurrentOperationProgress(66);
309 }
310 catch(HRESULT aRC)
311 {
312 rc = aRC;
313 }
314
315 /* Clean up */
316 if (pszPath)
317 RTStrFree(pszPath);
318 if (pDir)
319 RTDirClose(pDir);
320
321 if(aTask->progress)
322 aTask->progress->SetCurrentOperationProgress(99);
323
324 /* Assign the result on success (this clears the old list) */
325 if (rc == S_OK)
326 m->entryList.assign(fileList.begin(), fileList.end());
327
328 aTask->rc = rc;
329
330 if (!aTask->progress.isNull())
331 aTask->progress->notifyComplete(rc);
332
333 LogFlowFunc(("rc=%Rhrc\n", rc));
334 LogFlowFuncLeave();
335
336 return VINF_SUCCESS;
337}
338
339HRESULT VFSExplorer::deleteFS(TaskVFSExplorer *aTask)
340{
341 LogFlowFuncEnter();
342
343 AutoCaller autoCaller(this);
344 if (FAILED(autoCaller.rc())) return autoCaller.rc();
345
346 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
347
348 HRESULT rc = S_OK;
349
350 float fPercentStep = 100.0f / aTask->filenames.size();
351 try
352 {
353 char szPath[RTPATH_MAX];
354 std::list<Utf8Str>::const_iterator it;
355 size_t i = 0;
356 for (it = aTask->filenames.begin();
357 it != aTask->filenames.end();
358 ++it, ++i)
359 {
360 memcpy(szPath, m->strPath.c_str(), strlen(m->strPath.c_str()) + 1);
361 RTPathStripFilename(szPath);
362 RTPathAppend(szPath, sizeof(szPath), (*it).c_str());
363 int vrc = RTFileDelete(szPath);
364 if (RT_FAILURE(vrc))
365 throw setError(VBOX_E_FILE_ERROR, tr ("Can't delete file '%s' (%Rrc)"), szPath, vrc);
366 if(aTask->progress)
367 aTask->progress->SetCurrentOperationProgress((ULONG)(fPercentStep * i));
368 }
369 }
370 catch(HRESULT aRC)
371 {
372 rc = aRC;
373 }
374
375 aTask->rc = rc;
376
377 if (!aTask->progress.isNull())
378 aTask->progress->notifyComplete(rc);
379
380 LogFlowFunc(("rc=%Rhrc\n", rc));
381 LogFlowFuncLeave();
382
383 return VINF_SUCCESS;
384}
385
386HRESULT VFSExplorer::updateS3(TaskVFSExplorer *aTask)
387{
388 LogFlowFuncEnter();
389
390 AutoCaller autoCaller(this);
391 if (FAILED(autoCaller.rc())) return autoCaller.rc();
392
393 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
394
395 HRESULT rc = S_OK;
396
397 RTS3 hS3 = NULL;
398 std::list<VFSExplorer::Data::DirEntry> fileList;
399 try
400 {
401 int vrc = RTS3Create(&hS3, m->strUsername.c_str(), m->strPassword.c_str(), m->strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
402 if (RT_FAILURE(vrc))
403 throw setError(E_FAIL, tr ("Can't open S3 storage service (%Rrc)"), vrc);
404
405 RTS3SetProgressCallback(hS3, VFSExplorer::TaskVFSExplorer::uploadProgress, &aTask);
406 /* Do we need the list of buckets or keys? */
407 if (m->strBucket.isEmpty())
408 {
409 PCRTS3BUCKETENTRY pBuckets = NULL;
410 vrc = RTS3GetBuckets(hS3, &pBuckets);
411 if (RT_FAILURE(vrc))
412 throw setError(E_FAIL, tr ("Can't get buckets (%Rrc)"), vrc);
413
414 PCRTS3BUCKETENTRY pTmpBuckets = pBuckets;
415 while (pBuckets)
416 {
417 fileList.push_back(VFSExplorer::Data::DirEntry(pBuckets->pszName, VFSFileType_Directory));
418 pBuckets = pBuckets->pNext;
419 }
420 RTS3BucketsDestroy(pTmpBuckets);
421 }
422 else
423 {
424 PCRTS3KEYENTRY pKeys = NULL;
425 vrc = RTS3GetBucketKeys(hS3, m->strBucket.c_str(), &pKeys);
426 if (RT_FAILURE(vrc))
427 throw setError(E_FAIL, tr ("Can't get keys for bucket (%Rrc)"), vrc);
428
429 PCRTS3KEYENTRY pTmpKeys = pKeys;
430 while (pKeys)
431 {
432 Utf8Str name(pKeys->pszName);
433 fileList.push_back(VFSExplorer::Data::DirEntry(pKeys->pszName, VFSFileType_File));
434 pKeys = pKeys->pNext;
435 }
436 RTS3KeysDestroy(pTmpKeys);
437 }
438 }
439 catch(HRESULT aRC)
440 {
441 rc = aRC;
442 }
443
444 if (hS3 != NULL)
445 RTS3Destroy(hS3);
446
447 /* Assign the result on success (this clears the old list) */
448 if (rc == S_OK)
449 m->entryList.assign(fileList.begin(), fileList.end());
450
451 aTask->rc = rc;
452
453 if (!aTask->progress.isNull())
454 aTask->progress->notifyComplete(rc);
455
456 LogFlowFunc(("rc=%Rhrc\n", rc));
457 LogFlowFuncLeave();
458
459 return VINF_SUCCESS;
460}
461
462HRESULT VFSExplorer::deleteS3(TaskVFSExplorer *aTask)
463{
464 LogFlowFuncEnter();
465
466 AutoCaller autoCaller(this);
467 if (FAILED(autoCaller.rc())) return autoCaller.rc();
468
469 AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
470
471 HRESULT rc = S_OK;
472
473 RTS3 hS3 = NULL;
474 float fPercentStep = 100.0f / aTask->filenames.size();
475 try
476 {
477 int vrc = RTS3Create(&hS3, m->strUsername.c_str(), m->strPassword.c_str(), m->strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING);
478 if (RT_FAILURE(vrc))
479 throw setError(E_FAIL, tr ("Can't open S3 storage service (%Rrc)"), vrc);
480
481 RTS3SetProgressCallback(hS3, VFSExplorer::TaskVFSExplorer::uploadProgress, &aTask);
482
483 std::list<Utf8Str>::const_iterator it;
484 size_t i = 0;
485 for (it = aTask->filenames.begin();
486 it != aTask->filenames.end();
487 ++it, ++i)
488 {
489 vrc = RTS3DeleteKey(hS3, m->strBucket.c_str(), (*it).c_str());
490 if (RT_FAILURE(vrc))
491 throw setError(VBOX_E_FILE_ERROR, tr ("Can't delete file '%s' (%Rrc)"), (*it).c_str(), vrc);
492 if(aTask->progress)
493 aTask->progress->SetCurrentOperationProgress((ULONG)(fPercentStep * i));
494 }
495 }
496 catch(HRESULT aRC)
497 {
498 rc = aRC;
499 }
500
501 aTask->rc = rc;
502
503 if (hS3 != NULL)
504 RTS3Destroy(hS3);
505
506 if (!aTask->progress.isNull())
507 aTask->progress->notifyComplete(rc);
508
509 LogFlowFunc(("rc=%Rhrc\n", rc));
510 LogFlowFuncLeave();
511
512 return VINF_SUCCESS;
513}
514
515STDMETHODIMP VFSExplorer::Update(IProgress **aProgress)
516{
517 CheckComArgOutPointerValid(aProgress);
518
519 AutoCaller autoCaller(this);
520 if (FAILED(autoCaller.rc())) return autoCaller.rc();
521
522 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
523
524 HRESULT rc = S_OK;
525
526 ComObjPtr<Progress> progress;
527 try
528 {
529 Bstr progressDesc = BstrFmt(tr("Update directory info for '%s'"),
530 m->strPath.raw());
531 /* Create the progress object */
532 progress.createObject();
533
534 rc = progress->init(mVirtualBox, static_cast<IVFSExplorer*>(this),
535 progressDesc,
536 TRUE /* aCancelable */);
537 if (FAILED(rc)) throw rc;
538
539 /* Initialize our worker task */
540 std::auto_ptr<TaskVFSExplorer> task(new TaskVFSExplorer(TaskVFSExplorer::Update, this, progress));
541
542 rc = task->startThread();
543 if (FAILED(rc)) throw rc;
544
545 /* Don't destruct on success */
546 task.release();
547 }
548 catch (HRESULT aRC)
549 {
550 rc = aRC;
551 }
552
553 if (SUCCEEDED(rc))
554 /* Return progress to the caller */
555 progress.queryInterfaceTo(aProgress);
556
557 return rc;
558}
559
560STDMETHODIMP VFSExplorer::Cd(IN_BSTR aDir, IProgress **aProgress)
561{
562 CheckComArgNotNull(aDir);
563 CheckComArgOutPointerValid(aProgress);
564
565 return E_NOTIMPL;
566}
567
568STDMETHODIMP VFSExplorer::CdUp(IProgress **aProgress)
569{
570 CheckComArgOutPointerValid(aProgress);
571
572 return E_NOTIMPL;
573}
574
575STDMETHODIMP VFSExplorer::EntryList(ComSafeArrayOut(BSTR, aNames), ComSafeArrayOut(VFSFileType_T, aTypes))
576{
577 if (ComSafeArrayOutIsNull(aNames) ||
578 ComSafeArrayOutIsNull(aTypes))
579 return E_POINTER;
580
581 AutoCaller autoCaller(this);
582 if (FAILED(autoCaller.rc())) return autoCaller.rc();
583
584 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
585
586 com::SafeArray<BSTR> sfaNames((ULONG)m->entryList.size());
587 com::SafeArray<ULONG> sfaTypes((VFSFileType_T)m->entryList.size());
588
589 std::list<VFSExplorer::Data::DirEntry>::const_iterator it;
590 size_t i = 0;
591 for (it = m->entryList.begin();
592 it != m->entryList.end();
593 ++it, ++i)
594 {
595 const VFSExplorer::Data::DirEntry &entry = (*it);
596 Bstr bstr(entry.name);
597 bstr.cloneTo(&sfaNames[i]);
598 sfaTypes[i] = entry.type;
599 }
600
601 sfaNames.detachTo(ComSafeArrayOutArg(aNames));
602 sfaTypes.detachTo(ComSafeArrayOutArg(aTypes));
603
604 return S_OK;
605}
606
607STDMETHODIMP VFSExplorer::Exists(ComSafeArrayIn(IN_BSTR, aNames), ComSafeArrayOut(BSTR, aExists))
608{
609 CheckComArgSafeArrayNotNull(aNames);
610
611 AutoCaller autoCaller(this);
612 if (FAILED(autoCaller.rc())) return autoCaller.rc();
613
614 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
615
616 com::SafeArray<IN_BSTR> sfaNames(ComSafeArrayInArg(aNames));
617 std::list<BSTR> listExists;
618
619 std::list<VFSExplorer::Data::DirEntry>::const_iterator it;
620 for (it = m->entryList.begin();
621 it != m->entryList.end();
622 ++it)
623 {
624 const VFSExplorer::Data::DirEntry &entry = (*it);
625 for (size_t a=0; a < sfaNames.size(); ++a)
626 {
627 if (entry.name == RTPathFilename(Utf8Str(sfaNames[a]).c_str()))
628 {
629 BSTR name;
630 Bstr tmp(sfaNames[a]); /* gcc-3.3 cruft */
631 tmp.cloneTo(&name);
632 listExists.push_back(name);
633 }
634 }
635 }
636
637 com::SafeArray<BSTR> sfaExists(listExists);
638 sfaExists.detachTo(ComSafeArrayOutArg(aExists));
639
640 return S_OK;
641}
642
643STDMETHODIMP VFSExplorer::Remove(ComSafeArrayIn(IN_BSTR, aNames), IProgress **aProgress)
644{
645 CheckComArgSafeArrayNotNull(aNames);
646 CheckComArgOutPointerValid(aProgress);
647
648 AutoCaller autoCaller(this);
649 if (FAILED(autoCaller.rc())) return autoCaller.rc();
650
651 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
652
653 HRESULT rc = S_OK;
654
655 com::SafeArray<IN_BSTR> sfaNames(ComSafeArrayInArg(aNames));
656
657 ComObjPtr<Progress> progress;
658 try
659 {
660 /* Create the progress object */
661 progress.createObject();
662
663 rc = progress->init(mVirtualBox, static_cast<IVFSExplorer*>(this),
664 Bstr(tr("Delete files")),
665 TRUE /* aCancelable */);
666 if (FAILED(rc)) throw rc;
667
668 /* Initialize our worker task */
669 std::auto_ptr<TaskVFSExplorer> task(new TaskVFSExplorer(TaskVFSExplorer::Delete, this, progress));
670
671 /* Add all filenames to delete as task data */
672 for (size_t a=0; a < sfaNames.size(); ++a)
673 task->filenames.push_back(Utf8Str(sfaNames[a]));
674
675 rc = task->startThread();
676 if (FAILED(rc)) throw rc;
677
678 /* Don't destruct on success */
679 task.release();
680 }
681 catch (HRESULT aRC)
682 {
683 rc = aRC;
684 }
685
686 if (SUCCEEDED(rc))
687 /* Return progress to the caller */
688 progress.queryInterfaceTo(aProgress);
689
690 return rc;
691}
692
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use