VirtualBox

source: vbox/trunk/src/VBox/Main/MediumImpl.cpp@ 23223

最後變更 在這個檔案從23223是 23223,由 vboxsync 提交於 16 年 前

API: big medium handling change and lots of assorted other cleanups and fixes

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Date Revision Author Id
檔案大小: 188.1 KB
 
1/* $Id: MediumImpl.cpp 23223 2009-09-22 15:50:03Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008-2009 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.alldomusa.eu.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "MediumImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27#include "VirtualBoxImpl.h"
28
29#include "Logging.h"
30
31#include <VBox/com/array.h>
32#include <VBox/com/SupportErrorInfo.h>
33
34#include <VBox/err.h>
35#include <VBox/settings.h>
36
37#include <iprt/param.h>
38#include <iprt/path.h>
39#include <iprt/file.h>
40#include <iprt/tcp.h>
41
42#include <algorithm>
43
44////////////////////////////////////////////////////////////////////////////////
45//
46// Medium data definition
47//
48////////////////////////////////////////////////////////////////////////////////
49
50/** Describes how a machine refers to this image. */
51struct BackRef
52{
53 /** Equality predicate for stdc++. */
54 struct EqualsTo : public std::unary_function <BackRef, bool>
55 {
56 explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
57
58 bool operator()(const argument_type &aThat) const
59 {
60 return aThat.machineId == machineId;
61 }
62
63 const Guid machineId;
64 };
65
66 typedef std::list<Guid> GuidList;
67
68 BackRef() : inCurState(false) {}
69
70 BackRef(const Guid &aMachineId, const Guid &aSnapshotId = Guid::Empty)
71 : machineId(aMachineId)
72 , inCurState(aSnapshotId.isEmpty())
73 {
74 if (!aSnapshotId.isEmpty())
75 snapshotIds.push_back(aSnapshotId);
76 }
77
78 Guid machineId;
79 bool inCurState : 1;
80 GuidList snapshotIds;
81};
82
83typedef std::list<BackRef> BackRefList;
84
85struct Medium::Data
86{
87 Data()
88 : state(MediumState_NotCreated),
89 size(0),
90 readers(0),
91 queryInfoSem(NIL_RTSEMEVENTMULTI),
92 queryInfoCallers(0),
93 accessibleInLock(false),
94 type(MediumType_Normal),
95 logicalSize(0),
96 hddOpenMode(OpenReadWrite),
97 autoReset(false),
98 setImageId(false),
99 setParentId(false),
100 hostDrive(FALSE),
101 implicit(false),
102 numCreateDiffTasks(0),
103 vdProgress(NULL),
104 vdDiskIfaces(NULL)
105 {}
106
107 const Guid id;
108 Bstr description;
109 MediumState_T state;
110 Bstr location;
111 Bstr locationFull;
112 uint64_t size;
113 Bstr lastAccessError;
114
115 BackRefList backRefs;
116
117 size_t readers;
118
119 RTSEMEVENTMULTI queryInfoSem;
120 size_t queryInfoCallers;
121
122 bool accessibleInLock : 1;
123
124 const Bstr format;
125 ComObjPtr<MediumFormat> formatObj;
126
127 MediumType_T type;
128 uint64_t logicalSize; /*< In MBytes. */
129
130 HDDOpenMode hddOpenMode;
131
132 BOOL autoReset : 1;
133
134 /** the following members are invalid after changing UUID on open */
135 BOOL setImageId : 1;
136 BOOL setParentId : 1;
137 const Guid imageId;
138 const Guid parentId;
139
140 BOOL hostDrive : 1;
141
142 typedef std::map <Bstr, Bstr> PropertyMap;
143 PropertyMap properties;
144
145 bool implicit : 1;
146
147 uint32_t numCreateDiffTasks;
148
149 Utf8Str vdError; /*< Error remembered by the VD error callback. */
150 Progress *vdProgress; /*< Progress for the VD progress callback. */
151
152 VDINTERFACE vdIfError;
153 VDINTERFACEERROR vdIfCallsError;
154
155 VDINTERFACE vdIfProgress;
156 VDINTERFACEPROGRESS vdIfCallsProgress;
157
158 VDINTERFACE vdIfConfig;
159 VDINTERFACECONFIG vdIfCallsConfig;
160
161 VDINTERFACE vdIfTcpNet;
162 VDINTERFACETCPNET vdIfCallsTcpNet;
163
164 PVDINTERFACE vdDiskIfaces;
165};
166
167////////////////////////////////////////////////////////////////////////////////
168//
169// Globals
170//
171////////////////////////////////////////////////////////////////////////////////
172
173/**
174 * Asynchronous task thread parameter bucket.
175 *
176 * Note that instances of this class must be created using new() because the
177 * task thread function will delete them when the task is complete!
178 *
179 * @note The constructor of this class adds a caller on the managed Medium
180 * object which is automatically released upon destruction.
181 */
182struct Medium::Task : public com::SupportErrorInfoBase
183{
184 enum Operation { CreateBase, CreateDiff,
185 Merge, Clone, Delete, Reset, Compact };
186
187 Medium *that;
188 VirtualBoxBaseProto::AutoCaller autoCaller;
189
190 ComObjPtr <Progress> progress;
191 Operation operation;
192
193 /** Where to save the result when executed using #runNow(). */
194 HRESULT rc;
195
196 Task(Medium *aThat, Progress *aProgress, Operation aOperation)
197 : that(aThat), autoCaller(aThat)
198 , progress(aProgress)
199 , operation(aOperation)
200 , rc(S_OK) {}
201
202 ~Task();
203
204 void setData(Medium *aTarget)
205 {
206 d.target = aTarget;
207 HRESULT rc = d.target->addCaller();
208 AssertComRC(rc);
209 }
210
211 void setData(Medium *aTarget, Medium *aParent)
212 {
213 d.target = aTarget;
214 HRESULT rc = d.target->addCaller();
215 AssertComRC(rc);
216 d.parentDisk = aParent;
217 if (aParent)
218 {
219 rc = d.parentDisk->addCaller();
220 AssertComRC(rc);
221 }
222 }
223
224 void setData(MergeChain *aChain)
225 {
226 AssertReturnVoid(aChain != NULL);
227 d.chain.reset(aChain);
228 }
229
230 void setData(ImageChain *aSrcChain, ImageChain *aParentChain)
231 {
232 AssertReturnVoid(aSrcChain != NULL);
233 AssertReturnVoid(aParentChain != NULL);
234 d.source.reset(aSrcChain);
235 d.parent.reset(aParentChain);
236 }
237
238 void setData(ImageChain *aImgChain)
239 {
240 AssertReturnVoid(aImgChain != NULL);
241 d.images.reset(aImgChain);
242 }
243
244 HRESULT startThread();
245 HRESULT runNow();
246
247 struct Data
248 {
249 Data() : size(0) {}
250
251 /* CreateBase */
252
253 uint64_t size;
254
255 /* CreateBase, CreateDiff, Clone */
256
257 MediumVariant_T variant;
258
259 /* CreateDiff, Clone */
260
261 ComObjPtr<Medium> target;
262
263 /* Clone */
264
265 /** Media to open, in {parent,child} order */
266 std::auto_ptr <ImageChain> source;
267 /** Media which are parent of target, in {parent,child} order */
268 std::auto_ptr <ImageChain> parent;
269 /** The to-be parent medium object */
270 ComObjPtr<Medium> parentDisk;
271
272 /* Merge */
273
274 /** Media to merge, in {parent,child} order */
275 std::auto_ptr <MergeChain> chain;
276
277 /* Compact */
278
279 /** Media to open, in {parent,child} order */
280 std::auto_ptr <ImageChain> images;
281 }
282 d;
283
284protected:
285
286 // SupportErrorInfoBase interface
287 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
288 const char *componentName() const { return Medium::ComponentName(); }
289};
290
291Medium::Task::~Task()
292{
293 /* remove callers added by setData() */
294 if (!d.target.isNull())
295 d.target->releaseCaller();
296}
297
298/**
299 * Starts a new thread driven by the Medium::taskThread() function and passes
300 * this Task instance as an argument.
301 *
302 * Note that if this method returns success, this Task object becomes an ownee
303 * of the started thread and will be automatically deleted when the thread
304 * terminates.
305 *
306 * @note When the task is executed by this method, IProgress::notifyComplete()
307 * is automatically called for the progress object associated with this
308 * task when the task is finished to signal the operation completion for
309 * other threads asynchronously waiting for it.
310 */
311HRESULT Medium::Task::startThread()
312{
313 int vrc = RTThreadCreate(NULL, Medium::taskThread, this,
314 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
315 "Medium::Task");
316 ComAssertMsgRCRet(vrc,
317 ("Could not create Medium::Task thread (%Rrc)\n", vrc),
318 E_FAIL);
319
320 return S_OK;
321}
322
323/**
324 * Runs Medium::taskThread() by passing it this Task instance as an argument
325 * on the current thread instead of creating a new one.
326 *
327 * This call implies that it is made on another temporary thread created for
328 * some asynchronous task. Avoid calling it from a normal thread since the task
329 * operatinos are potentially lengthy and will block the calling thread in this
330 * case.
331 *
332 * Note that this Task object will be deleted by taskThread() when this method
333 * returns!
334 *
335 * @note When the task is executed by this method, IProgress::notifyComplete()
336 * is not called for the progress object associated with this task when
337 * the task is finished. Instead, the result of the operation is returned
338 * by this method directly and it's the caller's responsibility to
339 * complete the progress object in this case.
340 */
341HRESULT Medium::Task::runNow()
342{
343 Medium::taskThread(NIL_RTTHREAD, this);
344
345 return rc;
346}
347
348////////////////////////////////////////////////////////////////////////////////
349//
350// Merge chain class
351//
352////////////////////////////////////////////////////////////////////////////////
353
354/**
355 * Helper class for merge operations.
356 *
357 * @note It is assumed that when modifying methods of this class are called,
358 * Medium::treeLock() is held in read mode.
359 */
360class Medium::MergeChain : public Medium::List,
361 public com::SupportErrorInfoBase
362{
363public:
364
365 MergeChain(bool aForward, bool aIgnoreAttachments)
366 : mForward(aForward)
367 , mIgnoreAttachments(aIgnoreAttachments) {}
368
369 ~MergeChain()
370 {
371 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
372 {
373 HRESULT rc = (*it)->UnlockWrite(NULL);
374 AssertComRC(rc);
375
376 (*it)->releaseCaller();
377 }
378
379 for (iterator it = begin(); it != end(); ++ it)
380 {
381 AutoWriteLock alock(*it);
382 Assert((*it)->m->state == MediumState_LockedWrite ||
383 (*it)->m->state == MediumState_Deleting);
384 if ((*it)->m->state == MediumState_LockedWrite)
385 (*it)->UnlockWrite(NULL);
386 else
387 (*it)->m->state = MediumState_Created;
388
389 (*it)->releaseCaller();
390 }
391
392 if (!mParent.isNull())
393 mParent->releaseCaller();
394 }
395
396 HRESULT addSource(Medium *aMedium)
397 {
398 HRESULT rc = aMedium->addCaller();
399 CheckComRCReturnRC(rc);
400
401 AutoWriteLock alock(aMedium);
402
403 if (mForward)
404 {
405 rc = checkChildrenAndAttachmentsAndImmutable(aMedium);
406 if (FAILED(rc))
407 {
408 aMedium->releaseCaller();
409 return rc;
410 }
411 }
412
413 /* We have to fetch the state with the COM method, cause it's possible
414 that the medium isn't fully initialized yet. See HRESULT
415 Medium::protectedInit(VirtualBox *aVirtualBox, const
416 settings::Key &aImageNode) for an explanation why. */
417 MediumState_T m;
418 rc = aMedium->COMGETTER(State)(&m);
419 CheckComRCReturnRC(rc);
420 /* go to Deleting */
421 switch (m)
422 {
423 case MediumState_Created:
424 aMedium->m->state = MediumState_Deleting;
425 break;
426 default:
427 aMedium->releaseCaller();
428 return aMedium->setStateError();
429 }
430
431 push_front(aMedium);
432
433 if (mForward)
434 {
435 /* we will need parent to reparent target */
436 if (!aMedium->mParent.isNull())
437 {
438 rc = aMedium->mParent->addCaller();
439 CheckComRCReturnRC(rc);
440
441 mParent = aMedium->mParent;
442 }
443 }
444 else
445 {
446 /* we will need to reparent children */
447 for (List::const_iterator it = aMedium->children().begin();
448 it != aMedium->children().end(); ++ it)
449 {
450 rc = (*it)->addCaller();
451 CheckComRCReturnRC(rc);
452
453 rc = (*it)->LockWrite(NULL);
454 if (FAILED(rc))
455 {
456 (*it)->releaseCaller();
457 return rc;
458 }
459
460 mChildren.push_back(*it);
461 }
462 }
463
464 return S_OK;
465 }
466
467 HRESULT addTarget(Medium *aMedium)
468 {
469 HRESULT rc = aMedium->addCaller();
470 CheckComRCReturnRC(rc);
471
472 AutoWriteLock alock(aMedium);
473
474 if (!mForward)
475 {
476 rc = checkChildrenAndImmutable(aMedium);
477 if (FAILED(rc))
478 {
479 aMedium->releaseCaller();
480 return rc;
481 }
482 }
483
484 /* go to LockedWrite */
485 rc = aMedium->LockWrite(NULL);
486 if (FAILED(rc))
487 {
488 aMedium->releaseCaller();
489 return rc;
490 }
491
492 push_front(aMedium);
493
494 return S_OK;
495 }
496
497 HRESULT addIntermediate(Medium *aMedium)
498 {
499 HRESULT rc = aMedium->addCaller();
500 CheckComRCReturnRC(rc);
501
502 AutoWriteLock alock(aMedium);
503
504 rc = checkChildrenAndAttachments(aMedium);
505 if (FAILED(rc))
506 {
507 aMedium->releaseCaller();
508 return rc;
509 }
510
511 /* go to Deleting */
512 switch (aMedium->m->state)
513 {
514 case MediumState_Created:
515 aMedium->m->state = MediumState_Deleting;
516 break;
517 default:
518 aMedium->releaseCaller();
519 return aMedium->setStateError();
520 }
521
522 push_front(aMedium);
523
524 return S_OK;
525 }
526
527 bool isForward() const { return mForward; }
528 Medium *parent() const { return mParent; }
529 const List &children() const { return mChildren; }
530
531 Medium *source() const
532 { AssertReturn(size() > 0, NULL); return mForward ? front() : back(); }
533
534 Medium *target() const
535 { AssertReturn(size() > 0, NULL); return mForward ? back() : front(); }
536
537protected:
538
539 // SupportErrorInfoBase interface
540 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
541 const char *componentName() const { return Medium::ComponentName(); }
542
543private:
544
545 HRESULT check(Medium *aMedium, bool aChildren, bool aAttachments,
546 bool aImmutable)
547 {
548 if (aChildren)
549 {
550 /* not going to multi-merge as it's too expensive */
551 if (aMedium->children().size() > 1)
552 {
553 return setError(E_FAIL,
554 tr("Medium '%ls' involved in the merge operation has more than one child medium (%d)"),
555 aMedium->m->locationFull.raw(),
556 aMedium->children().size());
557 }
558 }
559
560 if (aAttachments && !mIgnoreAttachments)
561 {
562 if (aMedium->m->backRefs.size() != 0)
563 return setError(E_FAIL,
564 tr("Medium '%ls' is attached to %d virtual machines"),
565 aMedium->m->locationFull.raw(),
566 aMedium->m->backRefs.size());
567 }
568
569 if (aImmutable)
570 {
571 if (aMedium->m->type == MediumType_Immutable)
572 return setError(E_FAIL,
573 tr("Medium '%ls' is immutable"),
574 aMedium->m->locationFull.raw());
575 }
576
577 return S_OK;
578 }
579
580 HRESULT checkChildren(Medium *aMedium)
581 { return check(aMedium, true, false, false); }
582
583 HRESULT checkChildrenAndImmutable(Medium *aMedium)
584 { return check(aMedium, true, false, true); }
585
586 HRESULT checkChildrenAndAttachments(Medium *aMedium)
587 { return check(aMedium, true, true, false); }
588
589 HRESULT checkChildrenAndAttachmentsAndImmutable(Medium *aMedium)
590 { return check(aMedium, true, true, true); }
591
592 /** true if forward merge, false if backward */
593 bool mForward : 1;
594 /** true to not perform attachment checks */
595 bool mIgnoreAttachments : 1;
596
597 /** Parent of the source when forward merge (if any) */
598 ComObjPtr <Medium> mParent;
599 /** Children of the source when backward merge (if any) */
600 List mChildren;
601};
602
603////////////////////////////////////////////////////////////////////////////////
604//
605// ImageChain class
606//
607////////////////////////////////////////////////////////////////////////////////
608
609/**
610 * Helper class for image operations involving the entire parent chain.
611 *
612 * @note It is assumed that when modifying methods of this class are called,
613 * Medium::treeLock() is held in read mode.
614 */
615class Medium::ImageChain : public Medium::List,
616 public com::SupportErrorInfoBase
617{
618public:
619
620 ImageChain() {}
621
622 ~ImageChain()
623 {
624 /* empty? */
625 if (begin() != end())
626 {
627 List::const_iterator last = end();
628 last--;
629 for (List::const_iterator it = begin(); it != end(); ++ it)
630 {
631 AutoWriteLock alock(*it);
632 if (it == last)
633 {
634 Assert( (*it)->m->state == MediumState_LockedRead
635 || (*it)->m->state == MediumState_LockedWrite);
636 if ((*it)->m->state == MediumState_LockedRead)
637 (*it)->UnlockRead(NULL);
638 else if ((*it)->m->state == MediumState_LockedWrite)
639 (*it)->UnlockWrite(NULL);
640 }
641 else
642 {
643 Assert((*it)->m->state == MediumState_LockedRead);
644 if ((*it)->m->state == MediumState_LockedRead)
645 (*it)->UnlockRead(NULL);
646 }
647
648 (*it)->releaseCaller();
649 }
650 }
651 }
652
653 HRESULT addImage(Medium *aMedium)
654 {
655 HRESULT rc = aMedium->addCaller();
656 CheckComRCReturnRC(rc);
657
658 push_front(aMedium);
659
660 return S_OK;
661 }
662
663 HRESULT lockImagesRead()
664 {
665 /* Lock all disks in the chain in {parent, child} order,
666 * and make sure they are accessible. */
667 /// @todo code duplication with SessionMachine::lockMedia, see below
668 ErrorInfoKeeper eik(true /* aIsNull */);
669 MultiResult mrc(S_OK);
670 for (List::const_iterator it = begin(); it != end(); ++ it)
671 {
672 HRESULT rc = S_OK;
673 MediumState_T mediaState;
674 rc = (*it)->LockRead(&mediaState);
675 CheckComRCReturnRC(rc);
676
677 if (mediaState == MediumState_Inaccessible)
678 {
679 rc = (*it)->COMGETTER(State)(&mediaState);
680 CheckComRCReturnRC(rc);
681 Assert(mediaState == MediumState_LockedRead);
682
683 /* Note that we locked the medium already, so use the error
684 * value to see if there was an accessibility failure */
685 Bstr error;
686 rc = (*it)->COMGETTER(LastAccessError)(error.asOutParam());
687 CheckComRCReturnRC(rc);
688
689 if (!error.isEmpty())
690 {
691 Bstr loc;
692 rc = (*it)->COMGETTER(Location)(loc.asOutParam());
693 CheckComRCThrowRC(rc);
694
695 /* collect multiple errors */
696 eik.restore();
697
698 /* be in sync with Medium::setStateError() */
699 Assert(!error.isEmpty());
700 mrc = setError(E_FAIL,
701 tr("Medium '%ls' is not accessible. %ls"),
702 loc.raw(), error.raw());
703
704 eik.fetch();
705 }
706 }
707 }
708
709 eik.restore();
710 CheckComRCReturnRC((HRESULT)mrc);
711
712 return S_OK;
713 }
714
715 HRESULT lockImagesReadAndLastWrite()
716 {
717 /* Lock all disks in the chain in {parent, child} order,
718 * and make sure they are accessible. */
719 /// @todo code duplication with SessionMachine::lockMedia, see below
720 ErrorInfoKeeper eik(true /* aIsNull */);
721 MultiResult mrc(S_OK);
722 List::const_iterator last = end();
723 last--;
724 for (List::const_iterator it = begin(); it != end(); ++ it)
725 {
726 HRESULT rc = S_OK;
727 MediumState_T mediaState;
728 if (it == last)
729 rc = (*it)->LockWrite(&mediaState);
730 else
731 rc = (*it)->LockRead(&mediaState);
732 CheckComRCReturnRC(rc);
733
734 if (mediaState == MediumState_Inaccessible)
735 {
736 rc = (*it)->COMGETTER(State)(&mediaState);
737 CheckComRCReturnRC(rc);
738 if (it == last)
739 Assert(mediaState == MediumState_LockedWrite);
740 else
741 Assert(mediaState == MediumState_LockedRead);
742
743 /* Note that we locked the medium already, so use the error
744 * value to see if there was an accessibility failure */
745 Bstr error;
746 rc = (*it)->COMGETTER(LastAccessError)(error.asOutParam());
747 CheckComRCReturnRC(rc);
748
749 if (!error.isEmpty())
750 {
751 Bstr loc;
752 rc = (*it)->COMGETTER(Location)(loc.asOutParam());
753 CheckComRCThrowRC(rc);
754
755 /* collect multiple errors */
756 eik.restore();
757
758 /* be in sync with Medium::setStateError() */
759 Assert(!error.isEmpty());
760 mrc = setError(E_FAIL,
761 tr("Medium '%ls' is not accessible. %ls"),
762 loc.raw(), error.raw());
763
764 eik.fetch();
765 }
766 }
767 }
768
769 eik.restore();
770 CheckComRCReturnRC((HRESULT) mrc);
771
772 return S_OK;
773 }
774
775protected:
776
777 // SupportErrorInfoBase interface
778 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
779 const char *componentName() const { return Medium::ComponentName(); }
780
781private:
782
783};
784
785
786////////////////////////////////////////////////////////////////////////////////
787//
788// Medium constructor / destructor
789//
790////////////////////////////////////////////////////////////////////////////////
791
792DEFINE_EMPTY_CTOR_DTOR(Medium)
793
794HRESULT Medium::FinalConstruct()
795{
796 m = new Data;
797
798 /* Initialize the callbacks of the VD error interface */
799 m->vdIfCallsError.cbSize = sizeof(VDINTERFACEERROR);
800 m->vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
801 m->vdIfCallsError.pfnError = vdErrorCall;
802 m->vdIfCallsError.pfnMessage = NULL;
803
804 /* Initialize the callbacks of the VD progress interface */
805 m->vdIfCallsProgress.cbSize = sizeof(VDINTERFACEPROGRESS);
806 m->vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
807 m->vdIfCallsProgress.pfnProgress = vdProgressCall;
808
809 /* Initialize the callbacks of the VD config interface */
810 m->vdIfCallsConfig.cbSize = sizeof(VDINTERFACECONFIG);
811 m->vdIfCallsConfig.enmInterface = VDINTERFACETYPE_CONFIG;
812 m->vdIfCallsConfig.pfnAreKeysValid = vdConfigAreKeysValid;
813 m->vdIfCallsConfig.pfnQuerySize = vdConfigQuerySize;
814 m->vdIfCallsConfig.pfnQuery = vdConfigQuery;
815
816 /* Initialize the callbacks of the VD TCP interface (we always use the host
817 * * IP stack for now) */
818 m->vdIfCallsTcpNet.cbSize = sizeof(VDINTERFACETCPNET);
819 m->vdIfCallsTcpNet.enmInterface = VDINTERFACETYPE_TCPNET;
820 m->vdIfCallsTcpNet.pfnClientConnect = RTTcpClientConnect;
821 m->vdIfCallsTcpNet.pfnClientClose = RTTcpClientClose;
822 m->vdIfCallsTcpNet.pfnSelectOne = RTTcpSelectOne;
823 m->vdIfCallsTcpNet.pfnRead = RTTcpRead;
824 m->vdIfCallsTcpNet.pfnWrite = RTTcpWrite;
825 m->vdIfCallsTcpNet.pfnFlush = RTTcpFlush;
826
827 /* Initialize the per-disk interface chain */
828 int vrc;
829 vrc = VDInterfaceAdd(&m->vdIfError,
830 "Medium::vdInterfaceError",
831 VDINTERFACETYPE_ERROR,
832 &m->vdIfCallsError, this, &m->vdDiskIfaces);
833 AssertRCReturn(vrc, E_FAIL);
834
835 vrc = VDInterfaceAdd(&m->vdIfProgress,
836 "Medium::vdInterfaceProgress",
837 VDINTERFACETYPE_PROGRESS,
838 &m->vdIfCallsProgress, this, &m->vdDiskIfaces);
839 AssertRCReturn(vrc, E_FAIL);
840 vrc = VDInterfaceAdd(&m->vdIfConfig,
841 "Medium::vdInterfaceConfig",
842 VDINTERFACETYPE_CONFIG,
843 &m->vdIfCallsConfig, this, &m->vdDiskIfaces);
844 AssertRCReturn(vrc, E_FAIL);
845
846 vrc = VDInterfaceAdd(&m->vdIfTcpNet,
847 "Medium::vdInterfaceTcpNet",
848 VDINTERFACETYPE_TCPNET,
849 &m->vdIfCallsTcpNet, this, &m->vdDiskIfaces);
850 AssertRCReturn(vrc, E_FAIL);
851
852 return S_OK;
853}
854
855void Medium::FinalRelease()
856{
857 uninit();
858
859 delete m;
860}
861
862/**
863 * Initializes the hard disk object without creating or opening an associated
864 * storage unit.
865 *
866 * For hard disks that don't have the VD_CAP_CREATE_FIXED or
867 * VD_CAP_CREATE_DYNAMIC capability (and therefore cannot be created or deleted
868 * with the means of VirtualBox) the associated storage unit is assumed to be
869 * ready for use so the state of the hard disk object will be set to Created.
870 *
871 * @param aVirtualBox VirtualBox object.
872 * @param aLocation Storage unit location.
873 */
874HRESULT Medium::init(VirtualBox *aVirtualBox,
875 CBSTR aFormat,
876 CBSTR aLocation)
877{
878 AssertReturn(aVirtualBox != NULL, E_FAIL);
879 AssertReturn(aFormat != NULL && *aFormat != '\0', E_FAIL);
880
881 /* Enclose the state transition NotReady->InInit->Ready */
882 AutoInitSpan autoInitSpan(this);
883 AssertReturn(autoInitSpan.isOk(), E_FAIL);
884
885 HRESULT rc = S_OK;
886
887 /* share VirtualBox weakly (parent remains NULL so far) */
888 unconst(mVirtualBox) = aVirtualBox;
889
890 /* register with VirtualBox early, since uninit() will
891 * unconditionally unregister on failure */
892 aVirtualBox->addDependentChild(this);
893
894 /* no storage yet */
895 m->state = MediumState_NotCreated;
896
897 /* cannot be a host drive */
898 m->hostDrive = false;
899
900 /* No storage unit is created yet, no need to queryInfo() */
901
902 rc = setFormat(aFormat);
903 CheckComRCReturnRC(rc);
904
905 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
906 {
907 rc = setLocation(aLocation);
908 CheckComRCReturnRC(rc);
909 }
910 else
911 {
912 rc = setLocation(aLocation);
913 CheckComRCReturnRC(rc);
914
915 /// @todo later we may want to use a pfnComposeLocation backend info
916 /// callback to generate a well-formed location value (based on the hard
917 /// disk properties we have) rather than allowing each caller to invent
918 /// its own (pseudo-)location.
919 }
920
921 if (!(m->formatObj->capabilities() &
922 (MediumFormatCapabilities_CreateFixed |
923 MediumFormatCapabilities_CreateDynamic)))
924 {
925 /* storage for hard disks of this format can neither be explicitly
926 * created by VirtualBox nor deleted, so we place the hard disk to
927 * Created state here and also add it to the registry */
928 m->state = MediumState_Created;
929 unconst(m->id).create();
930 rc = mVirtualBox->registerHardDisk(this);
931
932 /// @todo later we may want to use a pfnIsConfigSufficient backend info
933 /// callback that would tell us when we have enough properties to work
934 /// with the hard disk and this information could be used to actually
935 /// move such hard disks from NotCreated to Created state. Instead of
936 /// pfnIsConfigSufficient we can use MediumFormat property
937 /// descriptions to see which properties are mandatory
938 }
939
940 /* Confirm a successful initialization when it's the case */
941 if (SUCCEEDED(rc))
942 autoInitSpan.setSucceeded();
943
944 return rc;
945}
946
947/**
948 * Initializes the hard disk object by opening the storage unit at the specified
949 * location. The enOpenMode parameter defines whether the image will be opened
950 * read/write or read-only.
951 *
952 * Note that the UUID, format and the parent of this hard disk will be
953 * determined when reading the hard disk storage unit, unless new values are
954 * specified by the parameters. If the detected or set parent is
955 * not known to VirtualBox, then this method will fail.
956 *
957 * @param aVirtualBox VirtualBox object.
958 * @param aLocation Storage unit location.
959 * @param enOpenMode Whether to open the image read/write or read-only.
960 * @param aSetImageId Whether to set the image UUID or not.
961 * @param aImageId New image UUID if @aSetId is true. Empty string means
962 * create a new UUID, and a zero UUID is invalid.
963 * @param aSetParentId Whether to set the parent UUID or not.
964 * @param aParentId New parent UUID if @aSetParentId is true. Empty string
965 * means create a new UUID, and a zero UUID is valid.
966 */
967HRESULT Medium::init(VirtualBox *aVirtualBox,
968 CBSTR aLocation,
969 HDDOpenMode enOpenMode,
970 BOOL aSetImageId,
971 const Guid &aImageId,
972 BOOL aSetParentId,
973 const Guid &aParentId)
974{
975 AssertReturn(aVirtualBox, E_INVALIDARG);
976 AssertReturn(aLocation, E_INVALIDARG);
977
978 /* Enclose the state transition NotReady->InInit->Ready */
979 AutoInitSpan autoInitSpan(this);
980 AssertReturn(autoInitSpan.isOk(), E_FAIL);
981
982 HRESULT rc = S_OK;
983
984 /* share VirtualBox weakly (parent remains NULL so far) */
985 unconst(mVirtualBox) = aVirtualBox;
986
987 /* register with VirtualBox early, since uninit() will
988 * unconditionally unregister on failure */
989 aVirtualBox->addDependentChild(this);
990
991 /* there must be a storage unit */
992 m->state = MediumState_Created;
993
994 /* cannot be a host drive */
995 m->hostDrive = false;
996
997 /* remember the open mode (defaults to ReadWrite) */
998 m->hddOpenMode = enOpenMode;
999
1000 rc = setLocation(aLocation);
1001 CheckComRCReturnRC(rc);
1002
1003 /* save the new uuid values, will be used by queryInfo() */
1004 m->setImageId = aSetImageId;
1005 unconst(m->imageId) = aImageId;
1006 m->setParentId = aSetParentId;
1007 unconst(m->parentId) = aParentId;
1008
1009 /* get all the information about the medium from the storage unit */
1010 rc = queryInfo();
1011
1012 if (SUCCEEDED(rc))
1013 {
1014 /* if the storage unit is not accessible, it's not acceptable for the
1015 * newly opened media so convert this into an error */
1016 if (m->state == MediumState_Inaccessible)
1017 {
1018 Assert(!m->lastAccessError.isEmpty());
1019 rc = setError(E_FAIL, Utf8Str(m->lastAccessError));
1020 }
1021 else
1022 {
1023 AssertReturn(!m->id.isEmpty(), E_FAIL);
1024
1025 /* storage format must be detected by queryInfo() if the medium is accessible */
1026 AssertReturn(!m->format.isNull(), E_FAIL);
1027 }
1028 }
1029
1030 /* Confirm a successful initialization when it's the case */
1031 if (SUCCEEDED(rc))
1032 autoInitSpan.setSucceeded();
1033
1034 return rc;
1035}
1036
1037/**
1038 * Initializes the medium object by loading its data from the given settings
1039 * node. In this mode, the image will always be opened read/write.
1040 *
1041 * @param aVirtualBox VirtualBox object.
1042 * @param aParent Parent medium disk or NULL for a root (base) medium.
1043 * @param aType Device type of the medium.
1044 * @param aNode Configuration settings.
1045 *
1046 * @note Locks VirtualBox lock for writing, treeLock() for writing.
1047 */
1048HRESULT Medium::init(VirtualBox *aVirtualBox,
1049 Medium *aParent,
1050 DeviceType_T aType,
1051 const settings::Medium &data)
1052{
1053 using namespace settings;
1054
1055 AssertReturn(aVirtualBox, E_INVALIDARG);
1056
1057 /* Enclose the state transition NotReady->InInit->Ready */
1058 AutoInitSpan autoInitSpan(this);
1059 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1060
1061 HRESULT rc = S_OK;
1062
1063 /* share VirtualBox and parent weakly */
1064 unconst(mVirtualBox) = aVirtualBox;
1065
1066 /* register with VirtualBox/parent early, since uninit() will
1067 * unconditionally unregister on failure */
1068 if (aParent == NULL)
1069 aVirtualBox->addDependentChild(this);
1070 else
1071 {
1072 /* we set mParent */
1073 AutoWriteLock treeLock(this->treeLock());
1074
1075 mParent = aParent;
1076 aParent->addDependentChild(this);
1077 }
1078
1079 /* see below why we don't call queryInfo() (and therefore treat the medium
1080 * as inaccessible for now */
1081 m->state = MediumState_Inaccessible;
1082 m->lastAccessError = tr("Accessibility check was not yet performed");
1083
1084 /* required */
1085 unconst(m->id) = data.uuid;
1086
1087 /* assume not a host drive */
1088 m->hostDrive = false;
1089
1090 /* optional */
1091 m->description = data.strDescription;
1092
1093 /* required */
1094 if (aType == DeviceType_HardDisk)
1095 {
1096 AssertReturn(!data.strFormat.isEmpty(), E_FAIL);
1097 rc = setFormat(Bstr(data.strFormat));
1098 CheckComRCReturnRC(rc);
1099 }
1100 else
1101 {
1102 /// @todo handle host drive settings here as well?
1103 if (!data.strFormat.isEmpty())
1104 rc = setFormat(Bstr(data.strFormat));
1105 else
1106 rc = setFormat(Bstr("RAW"));
1107 CheckComRCReturnRC(rc);
1108 }
1109
1110 /* optional, only for diffs, default is false */
1111 if (aParent != NULL)
1112 m->autoReset = data.fAutoReset;
1113 else
1114 m->autoReset = false;
1115
1116 /* properties (after setting the format as it populates the map). Note that
1117 * if some properties are not supported but preseint in the settings file,
1118 * they will still be read and accessible (for possible backward
1119 * compatibility; we can also clean them up from the XML upon next
1120 * XML format version change if we wish) */
1121 for (settings::PropertiesMap::const_iterator it = data.properties.begin();
1122 it != data.properties.end(); ++ it)
1123 {
1124 const Utf8Str &name = it->first;
1125 const Utf8Str &value = it->second;
1126 m->properties[Bstr(name)] = Bstr(value);
1127 }
1128
1129 /* required */
1130 rc = setLocation(data.strLocation);
1131 CheckComRCReturnRC(rc);
1132
1133 if (aType == DeviceType_HardDisk)
1134 {
1135 /* type is only for base hard disks */
1136 if (mParent.isNull())
1137 m->type = data.hdType;
1138 }
1139 else
1140 m->type = MediumType_Writethrough;
1141
1142 LogFlowThisFunc(("m->locationFull='%ls', m->format=%ls, m->id={%RTuuid}\n",
1143 m->locationFull.raw(), m->format.raw(), m->id.raw()));
1144
1145 /* Don't call queryInfo() for registered media to prevent the calling
1146 * thread (i.e. the VirtualBox server startup thread) from an unexpected
1147 * freeze but mark it as initially inaccessible instead. The vital UUID,
1148 * location and format properties are read from the registry file above; to
1149 * get the actual state and the rest of the data, the user will have to call
1150 * COMGETTER(State). */
1151
1152 /* load all children */
1153 for (settings::MediaList::const_iterator it = data.llChildren.begin();
1154 it != data.llChildren.end(); ++ it)
1155 {
1156 const settings::Medium &m = *it;
1157
1158 ComObjPtr<Medium> pHD;
1159 pHD.createObject();
1160 rc = pHD->init(aVirtualBox,
1161 this, // parent
1162 aType,
1163 m); // child data
1164 CheckComRCBreakRC(rc);
1165
1166 rc = mVirtualBox->registerHardDisk(pHD, false /* aSaveRegistry */);
1167 CheckComRCBreakRC(rc);
1168 }
1169
1170 /* Confirm a successful initialization when it's the case */
1171 if (SUCCEEDED(rc))
1172 autoInitSpan.setSucceeded();
1173
1174 return rc;
1175}
1176
1177/**
1178 * Initializes the medium object by providing the host drive information.
1179 * Not used for anything but the host floppy/host DVD case.
1180 *
1181 * @todo optimize all callers to avoid reconstructing objects with the same
1182 * information over and over again - in the typical case each VM referring to
1183 * a particular host drive has its own instance.
1184 *
1185 * @param aVirtualBox VirtualBox object.
1186 * @param aType Device type of the medium.
1187 * @param aLocation Location of the host drive.
1188 * @param aDescription Comment for this host drive.
1189 *
1190 * @note Locks VirtualBox lock for writing, treeLock() for writing.
1191 */
1192HRESULT Medium::init(VirtualBox *aVirtualBox,
1193 DeviceType_T aType,
1194 CBSTR aLocation,
1195 CBSTR aDescription)
1196{
1197 ComAssertRet(aType == DeviceType_DVD || aType == DeviceType_Floppy, E_INVALIDARG);
1198 ComAssertRet(aLocation, E_INVALIDARG);
1199
1200 /* Enclose the state transition NotReady->InInit->Ready */
1201 AutoInitSpan autoInitSpan(this);
1202 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1203
1204 /* share VirtualBox weakly (parent remains NULL so far) */
1205 unconst(mVirtualBox) = aVirtualBox;
1206 aVirtualBox->addDependentChild(this);
1207
1208 /* fake up a UUID which is unique, but also reproducible */
1209 RTUUID uuid;
1210 RTUuidClear(&uuid);
1211 if (aType == DeviceType_DVD)
1212 memcpy(&uuid.au8[0], "DVD", 3);
1213 else
1214 memcpy(&uuid.au8[0], "FD", 2);
1215 /* use device name, adjusted to the end of uuid, shortened if necessary */
1216 Utf8Str loc(aLocation);
1217 size_t cbLocation = strlen(loc.raw());
1218 if (cbLocation > 12)
1219 memcpy(&uuid.au8[4], loc.raw() + (cbLocation - 12), 12);
1220 else
1221 memcpy(&uuid.au8[4 + 12 - cbLocation], loc.raw(), cbLocation);
1222 unconst(m->id) = uuid;
1223
1224 m->type = MediumType_Writethrough;
1225 m->state = MediumState_Created;
1226 m->hostDrive = true;
1227 HRESULT rc = setFormat(Bstr("RAW"));
1228 CheckComRCReturnRC(rc);
1229 rc = setLocation(aLocation);
1230 CheckComRCReturnRC(rc);
1231 m->description = aDescription;
1232
1233/// @todo generate uuid (similarly to host network interface uuid) from location and device type
1234
1235 autoInitSpan.setSucceeded();
1236 return S_OK;
1237}
1238
1239/**
1240 * Uninitializes the instance.
1241 *
1242 * Called either from FinalRelease() or by the parent when it gets destroyed.
1243 *
1244 * @note All children of this hard disk get uninitialized by calling their
1245 * uninit() methods.
1246 *
1247 * @note Locks treeLock() for writing, VirtualBox for writing.
1248 */
1249void Medium::uninit()
1250{
1251 /* Enclose the state transition Ready->InUninit->NotReady */
1252 AutoUninitSpan autoUninitSpan(this);
1253 if (autoUninitSpan.uninitDone())
1254 return;
1255
1256 if (!m->formatObj.isNull())
1257 {
1258 /* remove the caller reference we added in setFormat() */
1259 m->formatObj->releaseCaller();
1260 m->formatObj.setNull();
1261 }
1262
1263 if (m->state == MediumState_Deleting)
1264 {
1265 /* we are being uninitialized after've been deleted by merge.
1266 * Reparenting has already been done so don't touch it here (we are
1267 * now orphans and remoeDependentChild() will assert) */
1268
1269 Assert(mParent.isNull());
1270 }
1271 else
1272 {
1273 /* we uninit children and reset mParent
1274 * and VirtualBox::removeDependentChild() needs a write lock */
1275 AutoMultiWriteLock2 alock(mVirtualBox->lockHandle(), this->treeLock());
1276
1277 uninitDependentChildren();
1278
1279 if (!mParent.isNull())
1280 {
1281 mParent->removeDependentChild(this);
1282 mParent.setNull();
1283 }
1284 else
1285 mVirtualBox->removeDependentChild(this);
1286 }
1287
1288 unconst(mVirtualBox).setNull();
1289}
1290
1291////////////////////////////////////////////////////////////////////////////////
1292//
1293// IMedium public methods
1294//
1295////////////////////////////////////////////////////////////////////////////////
1296
1297STDMETHODIMP Medium::COMGETTER(Id)(BSTR *aId)
1298{
1299 CheckComArgOutPointerValid(aId);
1300
1301 AutoCaller autoCaller(this);
1302 CheckComRCReturnRC(autoCaller.rc());
1303
1304 AutoReadLock alock(this);
1305
1306 m->id.toUtf16().cloneTo(aId);
1307
1308 return S_OK;
1309}
1310
1311STDMETHODIMP Medium::COMGETTER(Description)(BSTR *aDescription)
1312{
1313 CheckComArgOutPointerValid(aDescription);
1314
1315 AutoCaller autoCaller(this);
1316 CheckComRCReturnRC(autoCaller.rc());
1317
1318 AutoReadLock alock(this);
1319
1320 if (m->description.isEmpty())
1321 Bstr("").cloneTo(aDescription);
1322 else
1323 m->description.cloneTo(aDescription);
1324
1325 return S_OK;
1326}
1327
1328STDMETHODIMP Medium::COMSETTER(Description)(IN_BSTR aDescription)
1329{
1330 CheckComArgNotNull(aDescription);
1331
1332 AutoCaller autoCaller(this);
1333 CheckComRCReturnRC(autoCaller.rc());
1334
1335 AutoWriteLock alock(this);
1336
1337 /// @todo update m->description and save the global registry (and local
1338 /// registries of portable VMs referring to this medium), this will also
1339 /// require to add the mRegistered flag to data
1340
1341 ReturnComNotImplemented();
1342}
1343
1344STDMETHODIMP Medium::COMGETTER(State)(MediumState_T *aState)
1345{
1346 CheckComArgOutPointerValid(aState);
1347
1348 AutoCaller autoCaller(this);
1349 CheckComRCReturnRC(autoCaller.rc());
1350
1351 /* queryInfo() locks this for writing. */
1352 AutoWriteLock alock(this);
1353
1354 HRESULT rc = S_OK;
1355
1356 switch (m->state)
1357 {
1358 case MediumState_Created:
1359 case MediumState_Inaccessible:
1360 case MediumState_LockedRead:
1361 case MediumState_LockedWrite:
1362 {
1363 rc = queryInfo();
1364 break;
1365 }
1366 default:
1367 break;
1368 }
1369
1370 *aState = m->state;
1371
1372 return rc;
1373}
1374
1375STDMETHODIMP Medium::COMGETTER(Location)(BSTR *aLocation)
1376{
1377 CheckComArgOutPointerValid(aLocation);
1378
1379 AutoCaller autoCaller(this);
1380 CheckComRCReturnRC(autoCaller.rc());
1381
1382 AutoReadLock alock(this);
1383
1384 m->locationFull.cloneTo(aLocation);
1385
1386 return S_OK;
1387}
1388
1389STDMETHODIMP Medium::COMSETTER(Location)(IN_BSTR aLocation)
1390{
1391 CheckComArgNotNull(aLocation);
1392
1393 AutoCaller autoCaller(this);
1394 CheckComRCReturnRC(autoCaller.rc());
1395
1396 AutoWriteLock alock(this);
1397
1398 /// @todo NEWMEDIA for file names, add the default extension if no extension
1399 /// is present (using the information from the VD backend which also implies
1400 /// that one more parameter should be passed to setLocation() requesting
1401 /// that functionality since it is only allwed when called from this method
1402
1403 /// @todo NEWMEDIA rename the file and set m->location on success, then save
1404 /// the global registry (and local registries of portable VMs referring to
1405 /// this medium), this will also require to add the mRegistered flag to data
1406
1407 ReturnComNotImplemented();
1408}
1409
1410STDMETHODIMP Medium::COMGETTER(Name)(BSTR *aName)
1411{
1412 CheckComArgOutPointerValid(aName);
1413
1414 AutoCaller autoCaller(this);
1415 CheckComRCReturnRC(autoCaller.rc());
1416
1417 AutoReadLock alock(this);
1418
1419 name().cloneTo(aName);
1420
1421 return S_OK;
1422}
1423
1424STDMETHODIMP Medium::COMGETTER(HostDrive)(BOOL *aHostDrive)
1425{
1426 CheckComArgOutPointerValid(aHostDrive);
1427
1428 AutoCaller autoCaller(this);
1429 CheckComRCReturnRC(autoCaller.rc());
1430
1431 AutoReadLock alock(this);
1432
1433 *aHostDrive = m->hostDrive;
1434
1435 return S_OK;
1436}
1437
1438STDMETHODIMP Medium::COMGETTER(Size)(ULONG64 *aSize)
1439{
1440 CheckComArgOutPointerValid(aSize);
1441
1442 AutoCaller autoCaller(this);
1443 CheckComRCReturnRC(autoCaller.rc());
1444
1445 AutoReadLock alock(this);
1446
1447 *aSize = m->size;
1448
1449 return S_OK;
1450}
1451
1452STDMETHODIMP Medium::COMGETTER(Format)(BSTR *aFormat)
1453{
1454 if (aFormat == NULL)
1455 return E_POINTER;
1456
1457 AutoCaller autoCaller(this);
1458 CheckComRCReturnRC(autoCaller.rc());
1459
1460 /* no need to lock, m->format is const */
1461 m->format.cloneTo(aFormat);
1462
1463 return S_OK;
1464}
1465
1466STDMETHODIMP Medium::COMGETTER(Type)(MediumType_T *aType)
1467{
1468 if (aType == NULL)
1469 return E_POINTER;
1470
1471 AutoCaller autoCaller(this);
1472 CheckComRCReturnRC(autoCaller.rc());
1473
1474 AutoReadLock alock(this);
1475
1476 *aType = m->type;
1477
1478 return S_OK;
1479}
1480
1481STDMETHODIMP Medium::COMSETTER(Type)(MediumType_T aType)
1482{
1483 AutoCaller autoCaller(this);
1484 CheckComRCReturnRC(autoCaller.rc());
1485
1486 /* VirtualBox::saveSettings() needs a write lock */
1487 AutoMultiWriteLock2 alock(mVirtualBox, this);
1488
1489 switch (m->state)
1490 {
1491 case MediumState_Created:
1492 case MediumState_Inaccessible:
1493 break;
1494 default:
1495 return setStateError();
1496 }
1497
1498 if (m->type == aType)
1499 {
1500 /* Nothing to do */
1501 return S_OK;
1502 }
1503
1504 /* we access mParent & children() */
1505 AutoReadLock treeLock(this->treeLock());
1506
1507 /* cannot change the type of a differencing hard disk */
1508 if (!mParent.isNull())
1509 return setError(E_FAIL,
1510 tr("Hard disk '%ls' is a differencing hard disk"),
1511 m->locationFull.raw());
1512
1513 /* cannot change the type of a hard disk being in use */
1514 if (m->backRefs.size() != 0)
1515 return setError(E_FAIL,
1516 tr("Hard disk '%ls' is attached to %d virtual machines"),
1517 m->locationFull.raw(), m->backRefs.size());
1518
1519 switch (aType)
1520 {
1521 case MediumType_Normal:
1522 case MediumType_Immutable:
1523 {
1524 /* normal can be easily converted to imutable and vice versa even
1525 * if they have children as long as they are not attached to any
1526 * machine themselves */
1527 break;
1528 }
1529 case MediumType_Writethrough:
1530 {
1531 /* cannot change to writethrough if there are children */
1532 if (children().size() != 0)
1533 return setError(E_FAIL,
1534 tr("Hard disk '%ls' has %d child hard disks"),
1535 children().size());
1536 break;
1537 }
1538 default:
1539 AssertFailedReturn(E_FAIL);
1540 }
1541
1542 m->type = aType;
1543
1544 HRESULT rc = mVirtualBox->saveSettings();
1545
1546 return rc;
1547}
1548
1549STDMETHODIMP Medium::COMGETTER(Parent)(IMedium **aParent)
1550{
1551 if (aParent == NULL)
1552 return E_POINTER;
1553
1554 AutoCaller autoCaller(this);
1555 CheckComRCReturnRC(autoCaller.rc());
1556
1557 /* we access mParent */
1558 AutoReadLock treeLock(this->treeLock());
1559
1560 mParent.queryInterfaceTo(aParent);
1561
1562 return S_OK;
1563}
1564
1565STDMETHODIMP Medium::COMGETTER(Children)(ComSafeArrayOut(IMedium *, aChildren))
1566{
1567 if (ComSafeArrayOutIsNull(aChildren))
1568 return E_POINTER;
1569
1570 AutoCaller autoCaller(this);
1571 CheckComRCReturnRC(autoCaller.rc());
1572
1573 /* we access children */
1574 AutoReadLock treeLock(this->treeLock());
1575
1576 SafeIfaceArray<IMedium> children(this->children());
1577 children.detachTo(ComSafeArrayOutArg(aChildren));
1578
1579 return S_OK;
1580}
1581
1582STDMETHODIMP Medium::COMGETTER(Base)(IMedium **aBase)
1583{
1584 if (aBase == NULL)
1585 return E_POINTER;
1586
1587 /* base() will do callers/locking */
1588
1589 base().queryInterfaceTo(aBase);
1590
1591 return S_OK;
1592}
1593
1594STDMETHODIMP Medium::COMGETTER(ReadOnly)(BOOL *aReadOnly)
1595{
1596 if (aReadOnly == NULL)
1597 return E_POINTER;
1598
1599 AutoCaller autoCaller(this);
1600 CheckComRCReturnRC(autoCaller.rc());
1601
1602 /* isRadOnly() will do locking */
1603
1604 *aReadOnly = isReadOnly();
1605
1606 return S_OK;
1607}
1608
1609STDMETHODIMP Medium::COMGETTER(LogicalSize)(ULONG64 *aLogicalSize)
1610{
1611 CheckComArgOutPointerValid(aLogicalSize);
1612
1613 {
1614 AutoCaller autoCaller(this);
1615 CheckComRCReturnRC(autoCaller.rc());
1616
1617 AutoReadLock alock(this);
1618
1619 /* we access mParent */
1620 AutoReadLock treeLock(this->treeLock());
1621
1622 if (mParent.isNull())
1623 {
1624 *aLogicalSize = m->logicalSize;
1625
1626 return S_OK;
1627 }
1628 }
1629
1630 /* We assume that some backend may decide to return a meaningless value in
1631 * response to VDGetSize() for differencing hard disks and therefore
1632 * always ask the base hard disk ourselves. */
1633
1634 /* base() will do callers/locking */
1635
1636 return base()->COMGETTER(LogicalSize)(aLogicalSize);
1637}
1638
1639STDMETHODIMP Medium::COMGETTER(AutoReset)(BOOL *aAutoReset)
1640{
1641 CheckComArgOutPointerValid(aAutoReset);
1642
1643 AutoCaller autoCaller(this);
1644 CheckComRCReturnRC(autoCaller.rc());
1645
1646 AutoReadLock alock(this);
1647
1648 if (mParent.isNull())
1649 *aAutoReset = FALSE;
1650
1651 *aAutoReset = m->autoReset;
1652
1653 return S_OK;
1654}
1655
1656STDMETHODIMP Medium::COMSETTER(AutoReset)(BOOL aAutoReset)
1657{
1658 AutoCaller autoCaller(this);
1659 CheckComRCReturnRC(autoCaller.rc());
1660
1661 /* VirtualBox::saveSettings() needs a write lock */
1662 AutoMultiWriteLock2 alock(mVirtualBox, this);
1663
1664 if (mParent.isNull())
1665 return setError(VBOX_E_NOT_SUPPORTED,
1666 tr("Hard disk '%ls' is not differencing"),
1667 m->locationFull.raw());
1668
1669 if (m->autoReset != aAutoReset)
1670 {
1671 m->autoReset = aAutoReset;
1672
1673 return mVirtualBox->saveSettings();
1674 }
1675
1676 return S_OK;
1677}
1678STDMETHODIMP Medium::COMGETTER(LastAccessError)(BSTR *aLastAccessError)
1679{
1680 CheckComArgOutPointerValid(aLastAccessError);
1681
1682 AutoCaller autoCaller(this);
1683 CheckComRCReturnRC(autoCaller.rc());
1684
1685 AutoReadLock alock(this);
1686
1687 if (m->lastAccessError.isEmpty())
1688 Bstr("").cloneTo(aLastAccessError);
1689 else
1690 m->lastAccessError.cloneTo(aLastAccessError);
1691
1692 return S_OK;
1693}
1694
1695STDMETHODIMP Medium::COMGETTER(MachineIds)(ComSafeArrayOut(BSTR,aMachineIds))
1696{
1697 if (ComSafeGUIDArrayOutIsNull(aMachineIds))
1698 return E_POINTER;
1699
1700 AutoCaller autoCaller(this);
1701 CheckComRCReturnRC(autoCaller.rc());
1702
1703 AutoReadLock alock(this);
1704
1705 com::SafeArray<BSTR> machineIds;
1706
1707 if (m->backRefs.size() != 0)
1708 {
1709 machineIds.reset(m->backRefs.size());
1710
1711 size_t i = 0;
1712 for (BackRefList::const_iterator it = m->backRefs.begin();
1713 it != m->backRefs.end(); ++ it, ++ i)
1714 {
1715 it->machineId.toUtf16().detachTo(&machineIds [i]);
1716 }
1717 }
1718
1719 machineIds.detachTo(ComSafeArrayOutArg(aMachineIds));
1720
1721 return S_OK;
1722}
1723
1724STDMETHODIMP Medium::GetSnapshotIds(IN_BSTR aMachineId,
1725 ComSafeArrayOut(BSTR, aSnapshotIds))
1726{
1727 CheckComArgExpr(aMachineId, Guid(aMachineId).isEmpty() == false);
1728 CheckComArgOutSafeArrayPointerValid(aSnapshotIds);
1729
1730 AutoCaller autoCaller(this);
1731 CheckComRCReturnRC(autoCaller.rc());
1732
1733 AutoReadLock alock(this);
1734
1735 com::SafeArray<BSTR> snapshotIds;
1736
1737 Guid id(aMachineId);
1738 for (BackRefList::const_iterator it = m->backRefs.begin();
1739 it != m->backRefs.end(); ++ it)
1740 {
1741 if (it->machineId == id)
1742 {
1743 size_t size = it->snapshotIds.size();
1744
1745 /* if the medium is attached to the machine in the current state, we
1746 * return its ID as the first element of the array */
1747 if (it->inCurState)
1748 ++ size;
1749
1750 if (size > 0)
1751 {
1752 snapshotIds.reset(size);
1753
1754 size_t j = 0;
1755 if (it->inCurState)
1756 it->machineId.toUtf16().detachTo(&snapshotIds [j ++]);
1757
1758 for (BackRef::GuidList::const_iterator jt =
1759 it->snapshotIds.begin();
1760 jt != it->snapshotIds.end(); ++ jt, ++ j)
1761 {
1762 (*jt).toUtf16().detachTo(&snapshotIds [j]);
1763 }
1764 }
1765
1766 break;
1767 }
1768 }
1769
1770 snapshotIds.detachTo(ComSafeArrayOutArg(aSnapshotIds));
1771
1772 return S_OK;
1773}
1774
1775/**
1776 * @note @a aState may be NULL if the state value is not needed (only for
1777 * in-process calls).
1778 */
1779STDMETHODIMP Medium::LockRead(MediumState_T *aState)
1780{
1781 AutoCaller autoCaller(this);
1782 CheckComRCReturnRC(autoCaller.rc());
1783
1784 AutoWriteLock alock(this);
1785
1786 /* return the current state before */
1787 if (aState)
1788 *aState = m->state;
1789
1790 HRESULT rc = S_OK;
1791
1792 switch (m->state)
1793 {
1794 case MediumState_Created:
1795 case MediumState_Inaccessible:
1796 case MediumState_LockedRead:
1797 {
1798 ++m->readers;
1799
1800 ComAssertMsgBreak(m->readers != 0, ("Counter overflow"), rc = E_FAIL);
1801
1802 if (m->state == MediumState_Created)
1803 m->accessibleInLock = true;
1804 else if (m->state == MediumState_Inaccessible)
1805 m->accessibleInLock = false;
1806
1807 m->state = MediumState_LockedRead;
1808
1809 break;
1810 }
1811 default:
1812 {
1813 rc = setStateError();
1814 break;
1815 }
1816 }
1817
1818 return rc;
1819}
1820
1821/**
1822 * @note @a aState may be NULL if the state value is not needed (only for
1823 * in-process calls).
1824 */
1825STDMETHODIMP Medium::UnlockRead(MediumState_T *aState)
1826{
1827 AutoCaller autoCaller(this);
1828 CheckComRCReturnRC(autoCaller.rc());
1829
1830 AutoWriteLock alock(this);
1831
1832 HRESULT rc = S_OK;
1833
1834 switch (m->state)
1835 {
1836 case MediumState_LockedRead:
1837 {
1838 if (m->queryInfoSem == NIL_RTSEMEVENTMULTI)
1839 {
1840 Assert(m->readers != 0);
1841 --m->readers;
1842
1843 /* Reset the state after the last reader */
1844 if (m->readers == 0)
1845 {
1846 if (m->accessibleInLock)
1847 m->state = MediumState_Created;
1848 else
1849 m->state = MediumState_Inaccessible;
1850 }
1851
1852 break;
1853 }
1854
1855 /* otherwise, queryInfo() is in progress; fall through */
1856 }
1857 default:
1858 {
1859 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1860 tr ("Medium '%ls' is not locked for reading"),
1861 m->locationFull.raw());
1862 break;
1863 }
1864 }
1865
1866 /* return the current state after */
1867 if (aState)
1868 *aState = m->state;
1869
1870 return rc;
1871}
1872
1873/**
1874 * @note @a aState may be NULL if the state value is not needed (only for
1875 * in-process calls).
1876 */
1877STDMETHODIMP Medium::LockWrite(MediumState_T *aState)
1878{
1879 AutoCaller autoCaller(this);
1880 CheckComRCReturnRC(autoCaller.rc());
1881
1882 AutoWriteLock alock(this);
1883
1884 /* return the current state before */
1885 if (aState)
1886 *aState = m->state;
1887
1888 HRESULT rc = S_OK;
1889
1890 switch (m->state)
1891 {
1892 case MediumState_Created:
1893 case MediumState_Inaccessible:
1894 {
1895 if (m->state == MediumState_Created)
1896 m->accessibleInLock = true;
1897 else if (m->state == MediumState_Inaccessible)
1898 m->accessibleInLock = false;
1899
1900 m->state = MediumState_LockedWrite;
1901 break;
1902 }
1903 default:
1904 {
1905 rc = setStateError();
1906 break;
1907 }
1908 }
1909
1910 return rc;
1911}
1912
1913/**
1914 * @note @a aState may be NULL if the state value is not needed (only for
1915 * in-process calls).
1916 */
1917STDMETHODIMP Medium::UnlockWrite(MediumState_T *aState)
1918{
1919 AutoCaller autoCaller(this);
1920 CheckComRCReturnRC(autoCaller.rc());
1921
1922 AutoWriteLock alock(this);
1923
1924 HRESULT rc = S_OK;
1925
1926 switch (m->state)
1927 {
1928 case MediumState_LockedWrite:
1929 {
1930 if (m->accessibleInLock)
1931 m->state = MediumState_Created;
1932 else
1933 m->state = MediumState_Inaccessible;
1934 break;
1935 }
1936 default:
1937 {
1938 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1939 tr ("Medium '%ls' is not locked for writing"),
1940 m->locationFull.raw());
1941 break;
1942 }
1943 }
1944
1945 /* return the current state after */
1946 if (aState)
1947 *aState = m->state;
1948
1949 return rc;
1950}
1951
1952STDMETHODIMP Medium::Close()
1953{
1954 AutoMayUninitSpan mayUninitSpan(this);
1955 CheckComRCReturnRC(mayUninitSpan.rc());
1956
1957 if (mayUninitSpan.alreadyInProgress())
1958 return S_OK;
1959
1960 /* unregisterWithVirtualBox() is assumed to always need a write mVirtualBox
1961 * lock as it is intenede to modify its internal structires. Also, we want
1962 * to unregister ourselves atomically after detecting that closure is
1963 * possible to make sure that we don't do that after another thread has done
1964 * VirtualBox::find*() but before it starts using us (provided that it holds
1965 * a mVirtualBox lock of course). */
1966
1967 AutoWriteLock vboxLock(mVirtualBox);
1968
1969 bool wasCreated = true;
1970
1971 switch (m->state)
1972 {
1973 case MediumState_NotCreated:
1974 wasCreated = false;
1975 break;
1976 case MediumState_Created:
1977 case MediumState_Inaccessible:
1978 break;
1979 default:
1980 return setStateError();
1981 }
1982
1983 if (m->backRefs.size() != 0)
1984 return setError(VBOX_E_OBJECT_IN_USE,
1985 tr("Medium '%ls' is attached to %d virtual machines"),
1986 m->locationFull.raw(), m->backRefs.size());
1987
1988 /* perform extra media-dependent close checks */
1989 HRESULT rc = canClose();
1990 CheckComRCReturnRC(rc);
1991
1992 if (wasCreated)
1993 {
1994 /* remove from the list of known media before performing actual
1995 * uninitialization (to keep the media registry consistent on
1996 * failure to do so) */
1997 rc = unregisterWithVirtualBox();
1998 CheckComRCReturnRC(rc);
1999 }
2000
2001 /* cause uninit() to happen on success */
2002 mayUninitSpan.acceptUninit();
2003
2004 return S_OK;
2005}
2006
2007STDMETHODIMP Medium::GetProperty(IN_BSTR aName, BSTR *aValue)
2008{
2009 CheckComArgStrNotEmptyOrNull(aName);
2010 CheckComArgOutPointerValid(aValue);
2011
2012 AutoCaller autoCaller(this);
2013 CheckComRCReturnRC(autoCaller.rc());
2014
2015 AutoReadLock alock(this);
2016
2017 Data::PropertyMap::const_iterator it = m->properties.find(Bstr(aName));
2018 if (it == m->properties.end())
2019 return setError(VBOX_E_OBJECT_NOT_FOUND,
2020 tr("Property '%ls' does not exist"), aName);
2021
2022 if (it->second.isEmpty())
2023 Bstr("").cloneTo(aValue);
2024 else
2025 it->second.cloneTo(aValue);
2026
2027 return S_OK;
2028}
2029
2030STDMETHODIMP Medium::SetProperty(IN_BSTR aName, IN_BSTR aValue)
2031{
2032 CheckComArgStrNotEmptyOrNull(aName);
2033
2034 AutoCaller autoCaller(this);
2035 CheckComRCReturnRC(autoCaller.rc());
2036
2037 /* VirtualBox::saveSettings() needs a write lock */
2038 AutoMultiWriteLock2 alock(mVirtualBox, this);
2039
2040 switch (m->state)
2041 {
2042 case MediumState_Created:
2043 case MediumState_Inaccessible:
2044 break;
2045 default:
2046 return setStateError();
2047 }
2048
2049 Data::PropertyMap::iterator it = m->properties.find(Bstr(aName));
2050 if (it == m->properties.end())
2051 return setError(VBOX_E_OBJECT_NOT_FOUND,
2052 tr("Property '%ls' does not exist"),
2053 aName);
2054
2055 if (aValue && !*aValue)
2056 it->second = (const char *)NULL;
2057 else
2058 it->second = aValue;
2059
2060 HRESULT rc = mVirtualBox->saveSettings();
2061
2062 return rc;
2063}
2064
2065STDMETHODIMP Medium::GetProperties(IN_BSTR aNames,
2066 ComSafeArrayOut(BSTR, aReturnNames),
2067 ComSafeArrayOut(BSTR, aReturnValues))
2068{
2069 CheckComArgOutSafeArrayPointerValid(aReturnNames);
2070 CheckComArgOutSafeArrayPointerValid(aReturnValues);
2071
2072 AutoCaller autoCaller(this);
2073 CheckComRCReturnRC(autoCaller.rc());
2074
2075 AutoReadLock alock(this);
2076
2077 /// @todo make use of aNames according to the documentation
2078 NOREF(aNames);
2079
2080 com::SafeArray<BSTR> names(m->properties.size());
2081 com::SafeArray<BSTR> values(m->properties.size());
2082 size_t i = 0;
2083
2084 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2085 it != m->properties.end();
2086 ++it)
2087 {
2088 it->first.cloneTo(&names[i]);
2089 if (it->second.isEmpty())
2090 Bstr("").cloneTo(&values [i]);
2091 else
2092 it->second.cloneTo(&values [i]);
2093 ++ i;
2094 }
2095
2096 names.detachTo(ComSafeArrayOutArg(aReturnNames));
2097 values.detachTo(ComSafeArrayOutArg(aReturnValues));
2098
2099 return S_OK;
2100}
2101
2102STDMETHODIMP Medium::SetProperties(ComSafeArrayIn(IN_BSTR, aNames),
2103 ComSafeArrayIn(IN_BSTR, aValues))
2104{
2105 CheckComArgSafeArrayNotNull(aNames);
2106 CheckComArgSafeArrayNotNull(aValues);
2107
2108 AutoCaller autoCaller(this);
2109 CheckComRCReturnRC(autoCaller.rc());
2110
2111 /* VirtualBox::saveSettings() needs a write lock */
2112 AutoMultiWriteLock2 alock(mVirtualBox, this);
2113
2114 com::SafeArray<IN_BSTR> names(ComSafeArrayInArg(aNames));
2115 com::SafeArray<IN_BSTR> values(ComSafeArrayInArg(aValues));
2116
2117 /* first pass: validate names */
2118 for (size_t i = 0;
2119 i < names.size();
2120 ++i)
2121 {
2122 if (m->properties.find(Bstr(names[i])) == m->properties.end())
2123 return setError(VBOX_E_OBJECT_NOT_FOUND,
2124 tr("Property '%ls' does not exist"), names[i]);
2125 }
2126
2127 /* second pass: assign */
2128 for (size_t i = 0;
2129 i < names.size();
2130 ++i)
2131 {
2132 Data::PropertyMap::iterator it = m->properties.find(Bstr(names[i]));
2133 AssertReturn(it != m->properties.end(), E_FAIL);
2134
2135 if (values[i] && !*values[i])
2136 it->second = (const char *)NULL;
2137 else
2138 it->second = values [i];
2139 }
2140
2141 HRESULT rc = mVirtualBox->saveSettings();
2142
2143 return rc;
2144}
2145
2146STDMETHODIMP Medium::CreateBaseStorage(ULONG64 aLogicalSize,
2147 MediumVariant_T aVariant,
2148 IProgress **aProgress)
2149{
2150 CheckComArgOutPointerValid(aProgress);
2151
2152 AutoCaller autoCaller(this);
2153 CheckComRCReturnRC(autoCaller.rc());
2154
2155 AutoWriteLock alock(this);
2156
2157 aVariant = (MediumVariant_T)((unsigned)aVariant & (unsigned)~MediumVariant_Diff);
2158 if ( !(aVariant & MediumVariant_Fixed)
2159 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2160 return setError(VBOX_E_NOT_SUPPORTED,
2161 tr("Hard disk format '%ls' does not support dynamic storage creation"),
2162 m->format.raw());
2163 if ( (aVariant & MediumVariant_Fixed)
2164 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2165 return setError(VBOX_E_NOT_SUPPORTED,
2166 tr("Hard disk format '%ls' does not support fixed storage creation"),
2167 m->format.raw());
2168
2169 switch (m->state)
2170 {
2171 case MediumState_NotCreated:
2172 break;
2173 default:
2174 return setStateError();
2175 }
2176
2177 ComObjPtr <Progress> progress;
2178 progress.createObject();
2179 /// @todo include fixed/dynamic
2180 HRESULT rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
2181 (aVariant & MediumVariant_Fixed)
2182 ? BstrFmt(tr("Creating fixed hard disk storage unit '%ls'"), m->locationFull.raw())
2183 : BstrFmt(tr("Creating dynamic hard disk storage unit '%ls'"), m->locationFull.raw()),
2184 TRUE /* aCancelable */);
2185 CheckComRCReturnRC(rc);
2186
2187 /* setup task object and thread to carry out the operation
2188 * asynchronously */
2189
2190 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateBase));
2191 AssertComRCReturnRC(task->autoCaller.rc());
2192
2193 task->d.size = aLogicalSize;
2194 task->d.variant = aVariant;
2195
2196 rc = task->startThread();
2197 CheckComRCReturnRC(rc);
2198
2199 /* go to Creating state on success */
2200 m->state = MediumState_Creating;
2201
2202 /* task is now owned by taskThread() so release it */
2203 task.release();
2204
2205 /* return progress to the caller */
2206 progress.queryInterfaceTo(aProgress);
2207
2208 return S_OK;
2209}
2210
2211STDMETHODIMP Medium::DeleteStorage(IProgress **aProgress)
2212{
2213 CheckComArgOutPointerValid(aProgress);
2214
2215 AutoCaller autoCaller(this);
2216 CheckComRCReturnRC(autoCaller.rc());
2217
2218 ComObjPtr <Progress> progress;
2219
2220 HRESULT rc = deleteStorageNoWait(progress);
2221 if (SUCCEEDED(rc))
2222 {
2223 /* return progress to the caller */
2224 progress.queryInterfaceTo(aProgress);
2225 }
2226
2227 return rc;
2228}
2229
2230STDMETHODIMP Medium::CreateDiffStorage(IMedium *aTarget,
2231 MediumVariant_T aVariant,
2232 IProgress **aProgress)
2233{
2234 CheckComArgNotNull(aTarget);
2235 CheckComArgOutPointerValid(aProgress);
2236
2237 AutoCaller autoCaller(this);
2238 CheckComRCReturnRC(autoCaller.rc());
2239
2240 ComObjPtr<Medium> diff;
2241 HRESULT rc = mVirtualBox->cast(aTarget, diff);
2242 CheckComRCReturnRC(rc);
2243
2244 AutoWriteLock alock(this);
2245
2246 if (m->type == MediumType_Writethrough)
2247 return setError(E_FAIL,
2248 tr("Hard disk '%ls' is Writethrough"),
2249 m->locationFull.raw());
2250
2251 /* We want to be locked for reading as long as our diff child is being
2252 * created */
2253 rc = LockRead(NULL);
2254 CheckComRCReturnRC(rc);
2255
2256 ComObjPtr <Progress> progress;
2257
2258 rc = createDiffStorageNoWait(diff, aVariant, progress);
2259 if (FAILED(rc))
2260 {
2261 HRESULT rc2 = UnlockRead(NULL);
2262 AssertComRC(rc2);
2263 /* Note: on success, taskThread() will unlock this */
2264 }
2265 else
2266 {
2267 /* return progress to the caller */
2268 progress.queryInterfaceTo(aProgress);
2269 }
2270
2271 return rc;
2272}
2273
2274STDMETHODIMP Medium::MergeTo(IN_BSTR /* aTargetId */, IProgress ** /* aProgress */)
2275{
2276 AutoCaller autoCaller(this);
2277 CheckComRCReturnRC(autoCaller.rc());
2278
2279 ReturnComNotImplemented();
2280}
2281
2282STDMETHODIMP Medium::CloneTo(IMedium *aTarget,
2283 MediumVariant_T aVariant,
2284 IMedium *aParent,
2285 IProgress **aProgress)
2286{
2287 CheckComArgNotNull(aTarget);
2288 CheckComArgOutPointerValid(aProgress);
2289
2290 AutoCaller autoCaller(this);
2291 CheckComRCReturnRC(autoCaller.rc());
2292
2293 ComObjPtr <Medium> target;
2294 HRESULT rc = mVirtualBox->cast(aTarget, target);
2295 CheckComRCReturnRC(rc);
2296 ComObjPtr <Medium> parent;
2297 if (aParent)
2298 {
2299 rc = mVirtualBox->cast(aParent, parent);
2300 CheckComRCReturnRC(rc);
2301 }
2302
2303 AutoMultiWriteLock3 alock(this, target, parent);
2304
2305 ComObjPtr <Progress> progress;
2306
2307 try
2308 {
2309 if ( target->m->state != MediumState_NotCreated
2310 && target->m->state != MediumState_Created)
2311 throw target->setStateError();
2312
2313 /** @todo separate out creating/locking an image chain from
2314 * SessionMachine::lockMedia and use it from here too.
2315 * logically this belongs into Medium functionality. */
2316
2317 /* Build the source chain and lock images in the proper order. */
2318 std::auto_ptr <ImageChain> srcChain(new ImageChain());
2319
2320 /* we walk the source tree */
2321 AutoReadLock srcTreeLock(this->treeLock());
2322 for (Medium *hd = this; hd; hd = hd->mParent)
2323 {
2324 rc = srcChain->addImage(hd);
2325 CheckComRCThrowRC(rc);
2326 }
2327 rc = srcChain->lockImagesRead();
2328 CheckComRCThrowRC(rc);
2329
2330 /* Build the parent chain and lock images in the proper order. */
2331 std::auto_ptr <ImageChain> parentChain(new ImageChain());
2332
2333 /* we walk the future parent tree */
2334 AutoReadLock parentTreeLock;
2335 if (parent)
2336 parentTreeLock.attach(parent->treeLock());
2337 for (Medium *hd = parent; hd; hd = hd->mParent)
2338 {
2339 rc = parentChain->addImage(hd);
2340 CheckComRCThrowRC(rc);
2341 }
2342 if (target->m->state == MediumState_Created)
2343 {
2344 /* If we're cloning to an existing image the parent chain also
2345 * contains the target image, and it gets locked for writing. */
2346 rc = parentChain->addImage(target);
2347 CheckComRCThrowRC(rc);
2348 rc = parentChain->lockImagesReadAndLastWrite();
2349 CheckComRCThrowRC(rc);
2350 }
2351 else
2352 {
2353 rc = parentChain->lockImagesRead();
2354 CheckComRCThrowRC(rc);
2355 }
2356
2357 progress.createObject();
2358 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2359 BstrFmt(tr("Creating clone hard disk '%ls'"),
2360 target->m->locationFull.raw()),
2361 TRUE /* aCancelable */);
2362 CheckComRCThrowRC(rc);
2363
2364 /* setup task object and thread to carry out the operation
2365 * asynchronously */
2366
2367 std::auto_ptr <Task> task(new Task(this, progress, Task::Clone));
2368 AssertComRCThrowRC(task->autoCaller.rc());
2369
2370 task->setData(target, parent);
2371 task->d.variant = aVariant;
2372 task->setData(srcChain.release(), parentChain.release());
2373
2374 rc = task->startThread();
2375 CheckComRCThrowRC(rc);
2376
2377 if (target->m->state == MediumState_NotCreated)
2378 {
2379 /* go to Creating state before leaving the lock */
2380 target->m->state = MediumState_Creating;
2381 }
2382
2383 /* task is now owned (or already deleted) by taskThread() so release it */
2384 task.release();
2385 }
2386 catch (HRESULT aRC)
2387 {
2388 rc = aRC;
2389 }
2390
2391 if (SUCCEEDED(rc))
2392 {
2393 /* return progress to the caller */
2394 progress.queryInterfaceTo(aProgress);
2395 }
2396
2397 return rc;
2398}
2399
2400STDMETHODIMP Medium::Compact(IProgress **aProgress)
2401{
2402 CheckComArgOutPointerValid(aProgress);
2403
2404 AutoCaller autoCaller(this);
2405 CheckComRCReturnRC(autoCaller.rc());
2406
2407 AutoWriteLock alock(this);
2408
2409 ComObjPtr <Progress> progress;
2410
2411 HRESULT rc = S_OK;
2412
2413 try
2414 {
2415 /** @todo separate out creating/locking an image chain from
2416 * SessionMachine::lockMedia and use it from here too.
2417 * logically this belongs into Medium functionality. */
2418
2419 /* Build the image chain and lock images in the proper order. */
2420 std::auto_ptr <ImageChain> imgChain(new ImageChain());
2421
2422 /* we walk the image tree */
2423 AutoReadLock srcTreeLock(this->treeLock());
2424 for (Medium *hd = this; hd; hd = hd->mParent)
2425 {
2426 rc = imgChain->addImage(hd);
2427 CheckComRCThrowRC(rc);
2428 }
2429 rc = imgChain->lockImagesReadAndLastWrite();
2430 CheckComRCThrowRC(rc);
2431
2432 progress.createObject();
2433 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2434 BstrFmt(tr("Compacting hard disk '%ls'"), m->locationFull.raw()),
2435 TRUE /* aCancelable */);
2436 CheckComRCThrowRC(rc);
2437
2438 /* setup task object and thread to carry out the operation
2439 * asynchronously */
2440
2441 std::auto_ptr <Task> task(new Task(this, progress, Task::Compact));
2442 AssertComRCThrowRC(task->autoCaller.rc());
2443
2444 task->setData(imgChain.release());
2445
2446 rc = task->startThread();
2447 CheckComRCThrowRC(rc);
2448
2449 /* task is now owned (or already deleted) by taskThread() so release it */
2450 task.release();
2451 }
2452 catch (HRESULT aRC)
2453 {
2454 rc = aRC;
2455 }
2456
2457 if (SUCCEEDED(rc))
2458 {
2459 /* return progress to the caller */
2460 progress.queryInterfaceTo(aProgress);
2461 }
2462
2463 return rc;
2464}
2465
2466STDMETHODIMP Medium::Reset(IProgress **aProgress)
2467{
2468 CheckComArgOutPointerValid(aProgress);
2469
2470 AutoCaller autoCaller(this);
2471 CheckComRCReturnRC(autoCaller.rc());
2472
2473 AutoWriteLock alock(this);
2474
2475 if (mParent.isNull())
2476 return setError(VBOX_E_NOT_SUPPORTED,
2477 tr ("Hard disk '%ls' is not differencing"),
2478 m->locationFull.raw());
2479
2480 HRESULT rc = canClose();
2481 CheckComRCReturnRC(rc);
2482
2483 rc = LockWrite(NULL);
2484 CheckComRCReturnRC(rc);
2485
2486 ComObjPtr <Progress> progress;
2487
2488 try
2489 {
2490 progress.createObject();
2491 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2492 BstrFmt(tr("Resetting differencing hard disk '%ls'"),
2493 m->locationFull.raw()),
2494 FALSE /* aCancelable */);
2495 CheckComRCThrowRC(rc);
2496
2497 /* setup task object and thread to carry out the operation
2498 * asynchronously */
2499
2500 std::auto_ptr <Task> task(new Task(this, progress, Task::Reset));
2501 AssertComRCThrowRC(task->autoCaller.rc());
2502
2503 rc = task->startThread();
2504 CheckComRCThrowRC(rc);
2505
2506 /* task is now owned (or already deleted) by taskThread() so release it */
2507 task.release();
2508 }
2509 catch (HRESULT aRC)
2510 {
2511 rc = aRC;
2512 }
2513
2514 if (FAILED (rc))
2515 {
2516 HRESULT rc2 = UnlockWrite(NULL);
2517 AssertComRC(rc2);
2518 /* Note: on success, taskThread() will unlock this */
2519 }
2520 else
2521 {
2522 /* return progress to the caller */
2523 progress.queryInterfaceTo(aProgress);
2524 }
2525
2526 return rc;
2527}
2528
2529////////////////////////////////////////////////////////////////////////////////
2530//
2531// Medium internal methods
2532//
2533////////////////////////////////////////////////////////////////////////////////
2534
2535/**
2536 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2537 * of this media and updates it if necessary to reflect the new location.
2538 *
2539 * @param aOldPath Old path (full).
2540 * @param aNewPath New path (full).
2541 *
2542 * @note Locks this object for writing.
2543 */
2544HRESULT Medium::updatePath(const char *aOldPath, const char *aNewPath)
2545{
2546 AssertReturn(aOldPath, E_FAIL);
2547 AssertReturn(aNewPath, E_FAIL);
2548
2549 AutoCaller autoCaller(this);
2550 CheckComRCReturnRC(autoCaller.rc());
2551
2552 AutoWriteLock alock(this);
2553
2554 LogFlowThisFunc(("locationFull.before='%s'\n", m->locationFull.raw()));
2555
2556 Utf8Str path = m->locationFull;
2557
2558 if (RTPathStartsWith(path.c_str(), aOldPath))
2559 {
2560 Utf8Str newPath = Utf8StrFmt("%s%s", aNewPath,
2561 path.raw() + strlen(aOldPath));
2562 path = newPath;
2563
2564 mVirtualBox->calculateRelativePath(path, path);
2565
2566 unconst(m->locationFull) = newPath;
2567 unconst(m->location) = path;
2568
2569 LogFlowThisFunc(("locationFull.after='%s'\n", m->locationFull.raw()));
2570 }
2571
2572 return S_OK;
2573}
2574
2575/**
2576 * Adds the given machine and optionally the snapshot to the list of the objects
2577 * this image is attached to.
2578 *
2579 * @param aMachineId Machine ID.
2580 * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
2581 */
2582HRESULT Medium::attachTo(const Guid &aMachineId,
2583 const Guid &aSnapshotId /*= Guid::Empty*/)
2584{
2585 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2586
2587 AutoCaller autoCaller(this);
2588 AssertComRCReturnRC(autoCaller.rc());
2589
2590 AutoWriteLock alock(this);
2591
2592 switch (m->state)
2593 {
2594 case MediumState_Created:
2595 case MediumState_Inaccessible:
2596 case MediumState_LockedRead:
2597 case MediumState_LockedWrite:
2598 break;
2599
2600 default:
2601 return setStateError();
2602 }
2603
2604 HRESULT rc = canAttach(aMachineId, aSnapshotId);
2605 CheckComRCReturnRC(rc);
2606
2607 BackRefList::iterator it =
2608 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2609 BackRef::EqualsTo(aMachineId));
2610 if (it == m->backRefs.end())
2611 {
2612 BackRef ref(aMachineId, aSnapshotId);
2613 m->backRefs.push_back(ref);
2614
2615 return S_OK;
2616 }
2617
2618 if (aSnapshotId.isEmpty())
2619 {
2620 /* sanity: no duplicate attachments */
2621 AssertReturn(!it->inCurState, E_FAIL);
2622 it->inCurState = true;
2623
2624 return S_OK;
2625 }
2626
2627 /* sanity: no duplicate attachments */
2628 BackRef::GuidList::const_iterator jt =
2629 std::find(it->snapshotIds.begin(), it->snapshotIds.end(), aSnapshotId);
2630 AssertReturn(jt == it->snapshotIds.end(), E_FAIL);
2631
2632 it->snapshotIds.push_back(aSnapshotId);
2633
2634 return S_OK;
2635}
2636
2637/**
2638 * Removes the given machine and optionally the snapshot from the list of the
2639 * objects this image is attached to.
2640 *
2641 * @param aMachineId Machine ID.
2642 * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
2643 * attachment.
2644 */
2645HRESULT Medium::detachFrom(const Guid &aMachineId,
2646 const Guid &aSnapshotId /*= Guid::Empty*/)
2647{
2648 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2649
2650 AutoCaller autoCaller(this);
2651 AssertComRCReturnRC(autoCaller.rc());
2652
2653 AutoWriteLock alock(this);
2654
2655 BackRefList::iterator it =
2656 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2657 BackRef::EqualsTo(aMachineId));
2658 AssertReturn(it != m->backRefs.end(), E_FAIL);
2659
2660 if (aSnapshotId.isEmpty())
2661 {
2662 /* remove the current state attachment */
2663 it->inCurState = false;
2664 }
2665 else
2666 {
2667 /* remove the snapshot attachment */
2668 BackRef::GuidList::iterator jt =
2669 std::find(it->snapshotIds.begin(), it->snapshotIds.end(), aSnapshotId);
2670
2671 AssertReturn(jt != it->snapshotIds.end(), E_FAIL);
2672 it->snapshotIds.erase(jt);
2673 }
2674
2675 /* if the backref becomes empty, remove it */
2676 if (it->inCurState == false && it->snapshotIds.size() == 0)
2677 m->backRefs.erase(it);
2678
2679 return S_OK;
2680}
2681
2682/**
2683 * Internal method to return the medium's GUID. Must have caller + locking!
2684 * @return
2685 */
2686const Guid& Medium::id() const
2687{
2688 return m->id;
2689}
2690
2691/**
2692 * Internal method to return the medium's GUID. Must have caller + locking!
2693 * @return
2694 */
2695MediumState_T Medium::state() const
2696{
2697 return m->state;
2698}
2699
2700/**
2701 * Internal method to return the medium's location. Must have caller + locking!
2702 * @return
2703 */
2704const Bstr& Medium::location() const
2705{
2706 return m->location;
2707}
2708
2709/**
2710 * Internal method to return the medium's full location. Must have caller + locking!
2711 * @return
2712 */
2713const Bstr& Medium::locationFull() const
2714{
2715 return m->locationFull;
2716}
2717
2718/**
2719 * Internal method to return the medium's list of backrefs. Must have caller + locking!
2720 * @return
2721 */
2722// const Medium::BackRefList& Medium::backRefs() const
2723// {
2724// return m->backRefs;
2725// }
2726
2727const Guid* Medium::getFirstMachineBackrefId() const
2728{
2729 if (!m->backRefs.size())
2730 return NULL;
2731
2732 return &m->backRefs.front().machineId;
2733}
2734
2735const Guid* Medium::getFirstMachineBackrefSnapshotId() const
2736{
2737 if (!m->backRefs.size())
2738 return NULL;
2739
2740 const BackRef &ref = m->backRefs.front();
2741 if (!ref.snapshotIds.size())
2742 return NULL;
2743
2744 return &ref.snapshotIds.front();
2745}
2746
2747/**
2748 * Internal method to check whether the medium is attached to the given machine. Must have caller + locking!
2749 * @return
2750 */
2751bool Medium::isAttachedTo(const Guid &aMachineId)
2752{
2753 BackRefList::iterator it =
2754 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2755 BackRef::EqualsTo(aMachineId));
2756 return it != m->backRefs.end() && it->inCurState;
2757}
2758
2759/**
2760 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2761 * of this hard disk or any its child and updates the paths if necessary to
2762 * reflect the new location.
2763 *
2764 * @param aOldPath Old path (full).
2765 * @param aNewPath New path (full).
2766 *
2767 * @note Locks treeLock() for reading, this object and all children for writing.
2768 */
2769void Medium::updatePaths(const char *aOldPath, const char *aNewPath)
2770{
2771 AssertReturnVoid(aOldPath);
2772 AssertReturnVoid(aNewPath);
2773
2774 AutoCaller autoCaller(this);
2775 AssertComRCReturnVoid(autoCaller.rc());
2776
2777 AutoWriteLock alock(this);
2778
2779 /* we access children() */
2780 AutoReadLock treeLock(this->treeLock());
2781
2782 updatePath(aOldPath, aNewPath);
2783
2784 /* update paths of all children */
2785 for (List::const_iterator it = children().begin();
2786 it != children().end();
2787 ++ it)
2788 {
2789 (*it)->updatePaths(aOldPath, aNewPath);
2790 }
2791}
2792
2793/**
2794 * Returns the base hard disk of the hard disk chain this hard disk is part of.
2795 *
2796 * The base hard disk is found by walking up the parent-child relationship axis.
2797 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
2798 * returns itself in response to this method.
2799 *
2800 * @param aLevel Where to store the number of ancestors of this hard disk
2801 * (zero for the base), may be @c NULL.
2802 *
2803 * @note Locks treeLock() for reading.
2804 */
2805ComObjPtr<Medium> Medium::base(uint32_t *aLevel /*= NULL*/)
2806{
2807 ComObjPtr <Medium> base;
2808 uint32_t level;
2809
2810 AutoCaller autoCaller(this);
2811 AssertReturn(autoCaller.isOk(), base);
2812
2813 /* we access mParent */
2814 AutoReadLock treeLock(this->treeLock());
2815
2816 base = this;
2817 level = 0;
2818
2819 if (!mParent.isNull())
2820 {
2821 for (;;)
2822 {
2823 AutoCaller baseCaller(base);
2824 AssertReturn(baseCaller.isOk(), base);
2825
2826 if (base->mParent.isNull())
2827 break;
2828
2829 base = base->mParent;
2830 ++ level;
2831 }
2832 }
2833
2834 if (aLevel != NULL)
2835 *aLevel = level;
2836
2837 return base;
2838}
2839
2840/**
2841 * Returns @c true if this hard disk cannot be modified because it has
2842 * dependants (children) or is part of the snapshot. Related to the hard disk
2843 * type and posterity, not to the current media state.
2844 *
2845 * @note Locks this object and treeLock() for reading.
2846 */
2847bool Medium::isReadOnly()
2848{
2849 AutoCaller autoCaller(this);
2850 AssertComRCReturn(autoCaller.rc(), false);
2851
2852 AutoReadLock alock(this);
2853
2854 /* we access children */
2855 AutoReadLock treeLock(this->treeLock());
2856
2857 switch (m->type)
2858 {
2859 case MediumType_Normal:
2860 {
2861 if (children().size() != 0)
2862 return true;
2863
2864 for (BackRefList::const_iterator it = m->backRefs.begin();
2865 it != m->backRefs.end(); ++ it)
2866 if (it->snapshotIds.size() != 0)
2867 return true;
2868
2869 return false;
2870 }
2871 case MediumType_Immutable:
2872 {
2873 return true;
2874 }
2875 case MediumType_Writethrough:
2876 {
2877 return false;
2878 }
2879 default:
2880 break;
2881 }
2882
2883 AssertFailedReturn(false);
2884}
2885
2886/**
2887 * Saves hard disk data by appending a new <HardDisk> child node to the given
2888 * parent node which can be either <HardDisks> or <HardDisk>.
2889 *
2890 * @param data Settings struct to be updated.
2891 *
2892 * @note Locks this object, treeLock() and children for reading.
2893 */
2894HRESULT Medium::saveSettings(settings::Medium &data)
2895{
2896 AutoCaller autoCaller(this);
2897 CheckComRCReturnRC(autoCaller.rc());
2898
2899 AutoReadLock alock(this);
2900
2901 /* we access mParent */
2902 AutoReadLock treeLock(this->treeLock());
2903
2904 data.uuid = m->id;
2905 data.strLocation = m->location;
2906 data.strFormat = m->format;
2907
2908 /* optional, only for diffs, default is false */
2909 if (!mParent.isNull())
2910 data.fAutoReset = !!m->autoReset;
2911 else
2912 data.fAutoReset = false;
2913
2914 /* optional */
2915 data.strDescription = m->description;
2916
2917 /* optional properties */
2918 data.properties.clear();
2919 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2920 it != m->properties.end();
2921 ++it)
2922 {
2923 /* only save properties that have non-default values */
2924 if (!it->second.isNull())
2925 {
2926 Utf8Str name = it->first;
2927 Utf8Str value = it->second;
2928 data.properties[name] = value;
2929 }
2930 }
2931
2932 /* only for base hard disks */
2933 if (mParent.isNull())
2934 data.hdType = m->type;
2935
2936 /* save all children */
2937 for (List::const_iterator it = children().begin();
2938 it != children().end();
2939 ++it)
2940 {
2941 settings::Medium m;
2942 HRESULT rc = (*it)->saveSettings(m);
2943 AssertComRCReturnRC(rc);
2944 data.llChildren.push_back(m);
2945 }
2946
2947 return S_OK;
2948}
2949
2950/**
2951 * Compares the location of this hard disk to the given location.
2952 *
2953 * The comparison takes the location details into account. For example, if the
2954 * location is a file in the host's filesystem, a case insensitive comparison
2955 * will be performed for case insensitive filesystems.
2956 *
2957 * @param aLocation Location to compare to (as is).
2958 * @param aResult Where to store the result of comparison: 0 if locations
2959 * are equal, 1 if this object's location is greater than
2960 * the specified location, and -1 otherwise.
2961 */
2962HRESULT Medium::compareLocationTo(const char *aLocation, int &aResult)
2963{
2964 AutoCaller autoCaller(this);
2965 AssertComRCReturnRC(autoCaller.rc());
2966
2967 AutoReadLock alock(this);
2968
2969 Utf8Str locationFull(m->locationFull);
2970
2971 /// @todo NEWMEDIA delegate the comparison to the backend?
2972
2973 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
2974 {
2975 Utf8Str location(aLocation);
2976
2977 /* For locations represented by files, append the default path if
2978 * only the name is given, and then get the full path. */
2979 if (!RTPathHavePath(aLocation))
2980 {
2981 location = Utf8StrFmt("%s%c%s",
2982 mVirtualBox->getDefaultHardDiskFolder().raw(),
2983 RTPATH_DELIMITER,
2984 aLocation);
2985 }
2986
2987 int vrc = mVirtualBox->calculateFullPath(location, location);
2988 if (RT_FAILURE(vrc))
2989 return setError(E_FAIL,
2990 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
2991 location.raw(),
2992 vrc);
2993
2994 aResult = RTPathCompare(locationFull.c_str(), location.c_str());
2995 }
2996 else
2997 aResult = locationFull.compare(aLocation);
2998
2999 return S_OK;
3000}
3001
3002/**
3003 * Checks that this hard disk may be discarded and performs necessary state
3004 * changes.
3005 *
3006 * This method is to be called prior to calling the #discard() to perform
3007 * necessary consistency checks and place involved hard disks to appropriate
3008 * states. If #discard() is not called or fails, the state modifications
3009 * performed by this method must be undone by #cancelDiscard().
3010 *
3011 * See #discard() for more info about discarding hard disks.
3012 *
3013 * @param aChain Where to store the created merge chain (may return NULL
3014 * if no real merge is necessary).
3015 *
3016 * @note Locks treeLock() for reading. Locks this object, aTarget and all
3017 * intermediate hard disks for writing.
3018 */
3019HRESULT Medium::prepareDiscard(MergeChain * &aChain)
3020{
3021 AutoCaller autoCaller(this);
3022 AssertComRCReturnRC(autoCaller.rc());
3023
3024 aChain = NULL;
3025
3026 AutoWriteLock alock(this);
3027
3028 /* we access mParent & children() */
3029 AutoReadLock treeLock(this->treeLock());
3030
3031 AssertReturn(m->type == MediumType_Normal, E_FAIL);
3032
3033 if (children().size() == 0)
3034 {
3035 /* special treatment of the last hard disk in the chain: */
3036
3037 if (mParent.isNull())
3038 {
3039 /* lock only, to prevent any usage; discard() will unlock */
3040 return LockWrite(NULL);
3041 }
3042
3043 /* the differencing hard disk w/o children will be deleted, protect it
3044 * from attaching to other VMs (this is why Deleting) */
3045
3046 switch (m->state)
3047 {
3048 case MediumState_Created:
3049 m->state = MediumState_Deleting;
3050 break;
3051 default:
3052 return setStateError();
3053 }
3054
3055 /* aChain is intentionally NULL here */
3056
3057 return S_OK;
3058 }
3059
3060 /* not going multi-merge as it's too expensive */
3061 if (children().size() > 1)
3062 return setError(E_FAIL,
3063 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
3064 m->locationFull.raw(), children().size());
3065
3066 /* this is a read-only hard disk with children; it must be associated with
3067 * exactly one snapshot (when the snapshot is being taken, none of the
3068 * current VM's hard disks may be attached to other VMs). Note that by the
3069 * time when discard() is called, there must be no any attachments at all
3070 * (the code calling prepareDiscard() should detach). */
3071 AssertReturn(m->backRefs.size() == 1 &&
3072 !m->backRefs.front().inCurState &&
3073 m->backRefs.front().snapshotIds.size() == 1, E_FAIL);
3074
3075 ComObjPtr<Medium> child = children().front();
3076
3077 /* we keep this locked, so lock the affected child to make sure the lock
3078 * order is correct when calling prepareMergeTo() */
3079 AutoWriteLock childLock(child);
3080
3081 /* delegate the rest to the profi */
3082 if (mParent.isNull())
3083 {
3084 /* base hard disk, backward merge */
3085
3086 Assert(child->m->backRefs.size() == 1);
3087 if (child->m->backRefs.front().machineId != m->backRefs.front().machineId)
3088 {
3089 /* backward merge is too tricky, we'll just detach on discard, so
3090 * lock only, to prevent any usage; discard() will only unlock
3091 * (since we return NULL in aChain) */
3092 return LockWrite(NULL);
3093 }
3094
3095 return child->prepareMergeTo(this, aChain, true /* aIgnoreAttachments */);
3096 }
3097 else
3098 {
3099 /* forward merge */
3100 return prepareMergeTo(child, aChain, true /* aIgnoreAttachments */);
3101 }
3102}
3103
3104/**
3105 * Discards this hard disk.
3106 *
3107 * Discarding the hard disk is merging its contents to its differencing child
3108 * hard disk (forward merge) or contents of its child hard disk to itself
3109 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
3110 * a differencing hard disk w/o children, then it will be simply deleted.
3111 * Calling this method on a base hard disk w/o children will do nothing and
3112 * silently succeed. If this hard disk has more than one child, the method will
3113 * currently return an error (since merging in this case would be too expensive
3114 * and result in data duplication).
3115 *
3116 * When the backward merge takes place (i.e. this hard disk is a target) then,
3117 * on success, this hard disk will automatically replace the differencing child
3118 * hard disk used as a source (which will then be deleted) in the attachment
3119 * this child hard disk is associated with. This will happen only if both hard
3120 * disks belong to the same machine because otherwise such a replace would be
3121 * too tricky and could be not expected by the other machine. Same relates to a
3122 * case when the child hard disk is not associated with any machine at all. When
3123 * the backward merge is not applied, the method behaves as if the base hard
3124 * disk were not attached at all -- i.e. simply detaches it from the machine but
3125 * leaves the hard disk chain intact.
3126 *
3127 * This method is basically a wrapper around #mergeTo() that selects the correct
3128 * merge direction and performs additional actions as described above and.
3129 *
3130 * Note that this method will not return until the merge operation is complete
3131 * (which may be quite time consuming depending on the size of the merged hard
3132 * disks).
3133 *
3134 * Note that #prepareDiscard() must be called before calling this method. If
3135 * this method returns a failure, the caller must call #cancelDiscard(). On
3136 * success, #cancelDiscard() must not be called (this method will perform all
3137 * necessary steps such as resetting states of all involved hard disks and
3138 * deleting @a aChain).
3139 *
3140 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3141 * no real merge takes place).
3142 *
3143 * @note Locks the hard disks from the chain for writing. Locks the machine
3144 * object when the backward merge takes place. Locks treeLock() lock for
3145 * reading or writing.
3146 */
3147HRESULT Medium::discard(ComObjPtr<Progress> &aProgress, MergeChain *aChain)
3148{
3149 AssertReturn(!aProgress.isNull(), E_FAIL);
3150
3151 ComObjPtr <Medium> hdFrom;
3152
3153 HRESULT rc = S_OK;
3154
3155 {
3156 AutoCaller autoCaller(this);
3157 AssertComRCReturnRC(autoCaller.rc());
3158
3159 aProgress->SetNextOperation(BstrFmt(tr("Discarding hard disk '%s'"), name().raw()),
3160 1); // weight
3161
3162 if (aChain == NULL)
3163 {
3164 AutoWriteLock alock(this);
3165
3166 /* we access mParent & children() */
3167 AutoReadLock treeLock(this->treeLock());
3168
3169 Assert(children().size() == 0);
3170
3171 /* special treatment of the last hard disk in the chain: */
3172
3173 if (mParent.isNull())
3174 {
3175 rc = UnlockWrite(NULL);
3176 AssertComRC(rc);
3177 return rc;
3178 }
3179
3180 /* delete the differencing hard disk w/o children */
3181
3182 Assert(m->state == MediumState_Deleting);
3183
3184 /* go back to Created since deleteStorage() expects this state */
3185 m->state = MediumState_Created;
3186
3187 hdFrom = this;
3188
3189 rc = deleteStorageAndWait(&aProgress);
3190 }
3191 else
3192 {
3193 hdFrom = aChain->source();
3194
3195 rc = hdFrom->mergeToAndWait(aChain, &aProgress);
3196 }
3197 }
3198
3199 if (SUCCEEDED(rc))
3200 {
3201 /* mergeToAndWait() cannot uninitialize the initiator because of
3202 * possible AutoCallers on the current thread, deleteStorageAndWait()
3203 * doesn't do it either; do it ourselves */
3204 hdFrom->uninit();
3205 }
3206
3207 return rc;
3208}
3209
3210/**
3211 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
3212 * or fails. Frees memory occupied by @a aChain.
3213 *
3214 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3215 * no real merge takes place).
3216 *
3217 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
3218 * reading.
3219 */
3220void Medium::cancelDiscard(MergeChain *aChain)
3221{
3222 AutoCaller autoCaller(this);
3223 AssertComRCReturnVoid(autoCaller.rc());
3224
3225 if (aChain == NULL)
3226 {
3227 AutoWriteLock alock(this);
3228
3229 /* we access mParent & children() */
3230 AutoReadLock treeLock(this->treeLock());
3231
3232 Assert(children().size() == 0);
3233
3234 /* special treatment of the last hard disk in the chain: */
3235
3236 if (mParent.isNull())
3237 {
3238 HRESULT rc = UnlockWrite(NULL);
3239 AssertComRC(rc);
3240 return;
3241 }
3242
3243 /* the differencing hard disk w/o children will be deleted, protect it
3244 * from attaching to other VMs (this is why Deleting) */
3245
3246 Assert(m->state == MediumState_Deleting);
3247 m->state = MediumState_Created;
3248
3249 return;
3250 }
3251
3252 /* delegate the rest to the profi */
3253 cancelMergeTo(aChain);
3254}
3255
3256/**
3257 * Returns a preferred format for differencing hard disks.
3258 */
3259Bstr Medium::preferredDiffFormat()
3260{
3261 Bstr format;
3262
3263 AutoCaller autoCaller(this);
3264 AssertComRCReturn(autoCaller.rc(), format);
3265
3266 /* m->format is const, no need to lock */
3267 format = m->format;
3268
3269 /* check that our own format supports diffs */
3270 if (!(m->formatObj->capabilities() & MediumFormatCapabilities_Differencing))
3271 {
3272 /* use the default format if not */
3273 AutoReadLock propsLock(mVirtualBox->systemProperties());
3274 format = mVirtualBox->getDefaultHardDiskFormat();
3275 }
3276
3277 return format;
3278}
3279
3280/**
3281 * Returns the medium type. Must have caller + locking!
3282 * @return
3283 */
3284MediumType_T Medium::type() const
3285{
3286 return m->type;
3287}
3288
3289/**
3290 * Returns VirtualBox::hardDiskTreeHandle(), for convenience. Don't forget
3291 * to follow these locking rules:
3292 *
3293 * 1. The write lock on this handle must be either held alone on the thread
3294 * or requested *after* the VirtualBox object lock. Mixing with other
3295 * locks is prohibited.
3296 *
3297 * 2. The read lock on this handle may be intermixed with any other lock
3298 * with the exception that it must be requested *after* the VirtualBox
3299 * object lock.
3300 */
3301RWLockHandle* Medium::treeLock()
3302{
3303 return mVirtualBox->hardDiskTreeLockHandle();
3304}
3305
3306// protected methods
3307////////////////////////////////////////////////////////////////////////////////
3308
3309/**
3310 * Returns a short version of the location attribute.
3311 *
3312 * @note Must be called from under this object's read or write lock.
3313 */
3314Utf8Str Medium::name()
3315{
3316 Utf8Str location(m->locationFull);
3317
3318 Utf8Str name = RTPathFilename(location.c_str());
3319 return name;
3320}
3321
3322/**
3323 * Sets the value of m->location and calculates the value of m->locationFull.
3324 *
3325 * Treats non-FS-path locations specially, and prepends the default hard disk
3326 * folder if the given location string does not contain any path information
3327 * at all.
3328 *
3329 * Also, if the specified location is a file path that ends with '/' then the
3330 * file name part will be generated by this method automatically in the format
3331 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
3332 * and assign to this medium, and <ext> is the default extension for this
3333 * medium's storage format. Note that this procedure requires the media state to
3334 * be NotCreated and will return a faiulre otherwise.
3335 *
3336 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
3337 * then it can be relative to the VirtualBox home directory.
3338 *
3339 * @note Must be called from under this object's write lock.
3340 */
3341HRESULT Medium::setLocation(const Utf8Str &aLocation)
3342{
3343 AssertReturn(!aLocation.isEmpty(), E_FAIL);
3344
3345 AutoCaller autoCaller(this);
3346 AssertComRCReturnRC(autoCaller.rc());
3347
3348 /* formatObj may be null only when initializing from an existing path and
3349 * no format is known yet */
3350 AssertReturn((!m->format.isNull() && !m->formatObj.isNull()) ||
3351 (autoCaller.state() == InInit &&
3352 m->state != MediumState_NotCreated && m->id.isEmpty() &&
3353 m->format.isNull() && m->formatObj.isNull()),
3354 E_FAIL);
3355
3356 /* are we dealing with a new hard disk constructed using the existing
3357 * location? */
3358 bool isImport = m->format.isNull();
3359
3360 if (isImport ||
3361 (m->formatObj->capabilities() & MediumFormatCapabilities_File))
3362 {
3363 Guid id;
3364
3365 Utf8Str location(aLocation);
3366
3367 if (m->state == MediumState_NotCreated)
3368 {
3369 /* must be a file (formatObj must be already known) */
3370 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3371
3372 if (RTPathFilename(location.c_str()) == NULL)
3373 {
3374 /* no file name is given (either an empty string or ends with a
3375 * slash), generate a new UUID + file name if the state allows
3376 * this */
3377
3378 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3379 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3380 E_FAIL);
3381
3382 Bstr ext = m->formatObj->fileExtensions().front();
3383 ComAssertMsgRet(!ext.isEmpty(),
3384 ("Default extension must not be empty\n"),
3385 E_FAIL);
3386
3387 id.create();
3388
3389 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3390 location.raw(), id.raw(), ext.raw());
3391 }
3392 }
3393
3394 /* append the default folder if no path is given */
3395 if (!RTPathHavePath(location.c_str()))
3396 location = Utf8StrFmt("%s%c%s",
3397 mVirtualBox->getDefaultHardDiskFolder().raw(),
3398 RTPATH_DELIMITER,
3399 location.raw());
3400
3401 /* get the full file name */
3402 Utf8Str locationFull;
3403 int vrc = mVirtualBox->calculateFullPath(location, locationFull);
3404 if (RT_FAILURE(vrc))
3405 return setError(VBOX_E_FILE_ERROR,
3406 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3407 location.raw(), vrc);
3408
3409 /* detect the backend from the storage unit if importing */
3410 if (isImport)
3411 {
3412 char *backendName = NULL;
3413
3414 /* is it a file? */
3415 {
3416 RTFILE file;
3417 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ);
3418 if (RT_SUCCESS(vrc))
3419 RTFileClose(file);
3420 }
3421 if (RT_SUCCESS(vrc))
3422 {
3423 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3424 }
3425 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3426 {
3427 /* assume it's not a file, restore the original location */
3428 location = locationFull = aLocation;
3429 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3430 }
3431
3432 if (RT_FAILURE(vrc))
3433 {
3434 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3435 return setError(VBOX_E_FILE_ERROR,
3436 tr("Could not find file for the hard disk '%s' (%Rrc)"),
3437 locationFull.raw(), vrc);
3438 else
3439 return setError(VBOX_E_IPRT_ERROR,
3440 tr("Could not get the storage format of the hard disk '%s' (%Rrc)"),
3441 locationFull.raw(), vrc);
3442 }
3443
3444 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3445
3446 HRESULT rc = setFormat(Bstr(backendName));
3447 RTStrFree(backendName);
3448
3449 /* setFormat() must not fail since we've just used the backend so
3450 * the format object must be there */
3451 AssertComRCReturnRC(rc);
3452 }
3453
3454 /* is it still a file? */
3455 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3456 {
3457 m->location = location;
3458 m->locationFull = locationFull;
3459
3460 if (m->state == MediumState_NotCreated)
3461 {
3462 /* assign a new UUID (this UUID will be used when calling
3463 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3464 * also do that if we didn't generate it to make sure it is
3465 * either generated by us or reset to null */
3466 unconst(m->id) = id;
3467 }
3468 }
3469 else
3470 {
3471 m->location = locationFull;
3472 m->locationFull = locationFull;
3473 }
3474 }
3475 else
3476 {
3477 m->location = aLocation;
3478 m->locationFull = aLocation;
3479 }
3480
3481 return S_OK;
3482}
3483
3484/**
3485 * Queries information from the image file.
3486 *
3487 * As a result of this call, the accessibility state and data members such as
3488 * size and description will be updated with the current information.
3489 *
3490 * @note This method may block during a system I/O call that checks storage
3491 * accessibility.
3492 *
3493 * @note Locks treeLock() for reading and writing (for new diff media checked
3494 * for the first time). Locks mParent for reading. Locks this object for
3495 * writing.
3496 */
3497HRESULT Medium::queryInfo()
3498{
3499 AutoWriteLock alock(this);
3500
3501 AssertReturn(m->state == MediumState_Created ||
3502 m->state == MediumState_Inaccessible ||
3503 m->state == MediumState_LockedRead ||
3504 m->state == MediumState_LockedWrite,
3505 E_FAIL);
3506
3507 HRESULT rc = S_OK;
3508
3509 int vrc = VINF_SUCCESS;
3510
3511 /* check if a blocking queryInfo() call is in progress on some other thread,
3512 * and wait for it to finish if so instead of querying data ourselves */
3513 if (m->queryInfoSem != NIL_RTSEMEVENTMULTI)
3514 {
3515 Assert(m->state == MediumState_LockedRead);
3516
3517 ++m->queryInfoCallers;
3518 alock.leave();
3519
3520 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3521
3522 alock.enter();
3523 --m->queryInfoCallers;
3524
3525 if (m->queryInfoCallers == 0)
3526 {
3527 /* last waiting caller deletes the semaphore */
3528 RTSemEventMultiDestroy(m->queryInfoSem);
3529 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3530 }
3531
3532 AssertRC(vrc);
3533
3534 return S_OK;
3535 }
3536
3537 /* lazily create a semaphore for possible callers */
3538 vrc = RTSemEventMultiCreate(&m->queryInfoSem);
3539 ComAssertRCRet(vrc, E_FAIL);
3540
3541 bool tempStateSet = false;
3542 if (m->state != MediumState_LockedRead &&
3543 m->state != MediumState_LockedWrite)
3544 {
3545 /* Cause other methods to prevent any modifications before leaving the
3546 * lock. Note that clients will never see this temporary state change
3547 * since any COMGETTER(State) is (or will be) blocked until we finish
3548 * and restore the actual state. */
3549 m->state = MediumState_LockedRead;
3550 tempStateSet = true;
3551 }
3552
3553 /* leave the lock before a blocking operation */
3554 alock.leave();
3555
3556 bool success = false;
3557 Utf8Str lastAccessError;
3558
3559 try
3560 {
3561 Utf8Str location(m->locationFull);
3562
3563 /* totally useless to do accessibility checks for host drives */
3564 if (m->hostDrive)
3565 {
3566 success = true;
3567 throw S_OK;
3568 }
3569
3570 /* are we dealing with a new hard disk constructed using the existing
3571 * location? */
3572 bool isImport = m->id.isEmpty();
3573
3574 PVBOXHDD hdd;
3575 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3576 ComAssertRCThrow(vrc, E_FAIL);
3577
3578 try
3579 {
3580 unsigned flags = VD_OPEN_FLAGS_INFO;
3581
3582 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3583 * hard disks because that would prevent necessary modifications
3584 * when opening hard disks of some third-party formats for the first
3585 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3586 * generate an UUID if it is missing) */
3587 if ( (m->hddOpenMode == OpenReadOnly)
3588 || !isImport
3589 )
3590 flags |= VD_OPEN_FLAGS_READONLY;
3591
3592 /** @todo This kind of opening of images is assuming that diff
3593 * images can be opened as base images. Should be fixed ASAP. */
3594 vrc = VDOpen(hdd,
3595 Utf8Str(m->format).c_str(),
3596 location.c_str(),
3597 flags,
3598 m->vdDiskIfaces);
3599 if (RT_FAILURE(vrc))
3600 {
3601 lastAccessError = Utf8StrFmt(tr("Could not open the hard disk '%ls'%s"),
3602 m->locationFull.raw(), vdError(vrc).raw());
3603 throw S_OK;
3604 }
3605
3606 if (m->formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3607 {
3608 /* modify the UUIDs if necessary */
3609 if (m->setImageId)
3610 {
3611 vrc = VDSetUuid(hdd, 0, m->imageId);
3612 ComAssertRCThrow(vrc, E_FAIL);
3613 }
3614 if (m->setParentId)
3615 {
3616 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3617 ComAssertRCThrow(vrc, E_FAIL);
3618 }
3619 /* zap the information, these are no long-term members */
3620 m->setImageId = false;
3621 unconst(m->imageId).clear();
3622 m->setParentId = false;
3623 unconst(m->parentId).clear();
3624
3625 /* check the UUID */
3626 RTUUID uuid;
3627 vrc = VDGetUuid(hdd, 0, &uuid);
3628 ComAssertRCThrow(vrc, E_FAIL);
3629
3630 if (isImport)
3631 {
3632 unconst(m->id) = uuid;
3633
3634 if (m->id.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3635 // only when importing a VDMK that has no UUID, create one in memory
3636 unconst(m->id).create();
3637 }
3638 else
3639 {
3640 Assert(!m->id.isEmpty());
3641
3642 if (m->id != uuid)
3643 {
3644 lastAccessError = Utf8StrFmt(
3645 tr("UUID {%RTuuid} of the hard disk '%ls' does not match the value {%RTuuid} stored in the media registry ('%ls')"),
3646 &uuid, m->locationFull.raw(), m->id.raw(),
3647 mVirtualBox->settingsFilePath().raw());
3648 throw S_OK;
3649 }
3650 }
3651 }
3652 else
3653 {
3654 /* the backend does not support storing UUIDs within the
3655 * underlying storage so use what we store in XML */
3656
3657 /* generate an UUID for an imported UUID-less hard disk */
3658 if (isImport)
3659 {
3660 if (m->setImageId)
3661 unconst(m->id) = m->imageId;
3662 else
3663 unconst(m->id).create();
3664 }
3665 }
3666
3667 /* check the type */
3668 unsigned uImageFlags;
3669 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3670 ComAssertRCThrow(vrc, E_FAIL);
3671
3672 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3673 {
3674 RTUUID parentId;
3675 vrc = VDGetParentUuid(hdd, 0, &parentId);
3676 ComAssertRCThrow(vrc, E_FAIL);
3677
3678 if (isImport)
3679 {
3680 /* the parent must be known to us. Note that we freely
3681 * call locking methods of mVirtualBox and parent from the
3682 * write lock (breaking the {parent,child} lock order)
3683 * because there may be no concurrent access to the just
3684 * opened hard disk on ther threads yet (and init() will
3685 * fail if this method reporst MediumState_Inaccessible) */
3686
3687 Guid id = parentId;
3688 ComObjPtr<Medium> parent;
3689 rc = mVirtualBox->findHardDisk(&id, NULL,
3690 false /* aSetError */,
3691 &parent);
3692 if (FAILED(rc))
3693 {
3694 lastAccessError = Utf8StrFmt(
3695 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%ls' is not found in the media registry ('%ls')"),
3696 &parentId, m->locationFull.raw(),
3697 mVirtualBox->settingsFilePath().raw());
3698 throw S_OK;
3699 }
3700
3701 /* deassociate from VirtualBox, associate with parent */
3702
3703 mVirtualBox->removeDependentChild(this);
3704
3705 /* we set mParent & children() */
3706 AutoWriteLock treeLock(this->treeLock());
3707
3708 Assert(mParent.isNull());
3709 mParent = parent;
3710 mParent->addDependentChild(this);
3711 }
3712 else
3713 {
3714 /* we access mParent */
3715 AutoReadLock treeLock(this->treeLock());
3716
3717 /* check that parent UUIDs match. Note that there's no need
3718 * for the parent's AutoCaller (our lifetime is bound to
3719 * it) */
3720
3721 if (mParent.isNull())
3722 {
3723 lastAccessError = Utf8StrFmt(
3724 tr("Hard disk '%ls' is differencing but it is not associated with any parent hard disk in the media registry ('%ls')"),
3725 m->locationFull.raw(),
3726 mVirtualBox->settingsFilePath().raw());
3727 throw S_OK;
3728 }
3729
3730 AutoReadLock parentLock(mParent);
3731 if (mParent->state() != MediumState_Inaccessible &&
3732 mParent->id() != parentId)
3733 {
3734 lastAccessError = Utf8StrFmt(
3735 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' does not match UUID {%RTuuid} of its parent hard disk stored in the media registry ('%ls')"),
3736 &parentId, m->locationFull.raw(),
3737 mParent->id().raw(),
3738 mVirtualBox->settingsFilePath().raw());
3739 throw S_OK;
3740 }
3741
3742 /// @todo NEWMEDIA what to do if the parent is not
3743 /// accessible while the diff is? Probably, nothing. The
3744 /// real code will detect the mismatch anyway.
3745 }
3746 }
3747
3748 m->size = VDGetFileSize(hdd, 0);
3749 m->logicalSize = VDGetSize(hdd, 0) / _1M;
3750
3751 success = true;
3752 }
3753 catch (HRESULT aRC)
3754 {
3755 rc = aRC;
3756 }
3757
3758 VDDestroy(hdd);
3759
3760 }
3761 catch (HRESULT aRC)
3762 {
3763 rc = aRC;
3764 }
3765
3766 alock.enter();
3767
3768 if (success)
3769 m->lastAccessError.setNull();
3770 else
3771 {
3772 m->lastAccessError = lastAccessError;
3773 LogWarningFunc(("'%ls' is not accessible (error='%ls', rc=%Rhrc, vrc=%Rrc)\n",
3774 m->locationFull.raw(), m->lastAccessError.raw(),
3775 rc, vrc));
3776 }
3777
3778 /* inform other callers if there are any */
3779 if (m->queryInfoCallers > 0)
3780 {
3781 RTSemEventMultiSignal(m->queryInfoSem);
3782 }
3783 else
3784 {
3785 /* delete the semaphore ourselves */
3786 RTSemEventMultiDestroy(m->queryInfoSem);
3787 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3788 }
3789
3790 if (tempStateSet)
3791 {
3792 /* Set the proper state according to the result of the check */
3793 if (success)
3794 m->state = MediumState_Created;
3795 else
3796 m->state = MediumState_Inaccessible;
3797 }
3798 else
3799 {
3800 /* we're locked, use a special field to store the result */
3801 m->accessibleInLock = success;
3802 }
3803
3804 return rc;
3805}
3806
3807/**
3808 * Sets the extended error info according to the current media state.
3809 *
3810 * @note Must be called from under this object's write or read lock.
3811 */
3812HRESULT Medium::setStateError()
3813{
3814 HRESULT rc = E_FAIL;
3815
3816 switch (m->state)
3817 {
3818 case MediumState_NotCreated:
3819 {
3820 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3821 tr("Storage for the medium '%ls' is not created"),
3822 m->locationFull.raw());
3823 break;
3824 }
3825 case MediumState_Created:
3826 {
3827 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3828 tr("Storage for the medium '%ls' is already created"),
3829 m->locationFull.raw());
3830 break;
3831 }
3832 case MediumState_LockedRead:
3833 {
3834 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3835 tr("Medium '%ls' is locked for reading by another task"),
3836 m->locationFull.raw());
3837 break;
3838 }
3839 case MediumState_LockedWrite:
3840 {
3841 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3842 tr("Medium '%ls' is locked for writing by another task"),
3843 m->locationFull.raw());
3844 break;
3845 }
3846 case MediumState_Inaccessible:
3847 {
3848 AssertMsg(!m->lastAccessError.isEmpty(),
3849 ("There must always be a reason for Inaccessible"));
3850
3851 /* be in sync with Console::powerUpThread() */
3852 if (!m->lastAccessError.isEmpty())
3853 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3854 tr("Medium '%ls' is not accessible. %ls"),
3855 m->locationFull.raw(), m->lastAccessError.raw());
3856 else
3857 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3858 tr("Medium '%ls' is not accessible"),
3859 m->locationFull.raw());
3860 break;
3861 }
3862 case MediumState_Creating:
3863 {
3864 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3865 tr("Storage for the medium '%ls' is being created"),
3866 m->locationFull.raw(), m->lastAccessError.raw());
3867 break;
3868 }
3869 case MediumState_Deleting:
3870 {
3871 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3872 tr("Storage for the medium '%ls' is being deleted"),
3873 m->locationFull.raw(), m->lastAccessError.raw());
3874 break;
3875 }
3876 default:
3877 {
3878 AssertFailed();
3879 break;
3880 }
3881 }
3882
3883 return rc;
3884}
3885
3886/**
3887 * Deletes the hard disk storage unit.
3888 *
3889 * If @a aProgress is not NULL but the object it points to is @c null then a new
3890 * progress object will be created and assigned to @a *aProgress on success,
3891 * otherwise the existing progress object is used. If Progress is NULL, then no
3892 * progress object is created/used at all.
3893 *
3894 * When @a aWait is @c false, this method will create a thread to perform the
3895 * delete operation asynchronously and will return immediately. Otherwise, it
3896 * will perform the operation on the calling thread and will not return to the
3897 * caller until the operation is completed. Note that @a aProgress cannot be
3898 * NULL when @a aWait is @c false (this method will assert in this case).
3899 *
3900 * @param aProgress Where to find/store a Progress object to track operation
3901 * completion.
3902 * @param aWait @c true if this method should block instead of creating
3903 * an asynchronous thread.
3904 *
3905 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
3906 * writing.
3907 */
3908HRESULT Medium::deleteStorage(ComObjPtr <Progress> *aProgress, bool aWait)
3909{
3910 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
3911
3912 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
3913 * ourselves atomically after detecting that deletion is possible to make
3914 * sure that we don't do that after another thread has done
3915 * VirtualBox::findHardDisk() but before it starts using us (provided that
3916 * it holds a mVirtualBox lock too of course). */
3917
3918 AutoWriteLock vboxLock(mVirtualBox);
3919
3920 AutoWriteLock alock(this);
3921
3922 if (!(m->formatObj->capabilities() &
3923 (MediumFormatCapabilities_CreateDynamic |
3924 MediumFormatCapabilities_CreateFixed)))
3925 return setError(VBOX_E_NOT_SUPPORTED,
3926 tr("Hard disk format '%ls' does not support storage deletion"),
3927 m->format.raw());
3928
3929 /* Note that we are fine with Inaccessible state too: a) for symmetry with
3930 * create calls and b) because it doesn't really harm to try, if it is
3931 * really inaccessibke, the delete operation will fail anyway. Accepting
3932 * Inaccessible state is especially important because all registered hard
3933 * disks are initially Inaccessible upon VBoxSVC startup until
3934 * COMGETTER(State) is called. */
3935
3936 switch (m->state)
3937 {
3938 case MediumState_Created:
3939 case MediumState_Inaccessible:
3940 break;
3941 default:
3942 return setStateError();
3943 }
3944
3945 if (m->backRefs.size() != 0)
3946 return setError(VBOX_E_OBJECT_IN_USE,
3947 tr("Hard disk '%ls' is attached to %d virtual machines"),
3948 m->locationFull.raw(), m->backRefs.size());
3949
3950 HRESULT rc = canClose();
3951 CheckComRCReturnRC(rc);
3952
3953 /* go to Deleting state before leaving the lock */
3954 m->state = MediumState_Deleting;
3955
3956 /* we need to leave this object's write lock now because of
3957 * unregisterWithVirtualBox() that locks treeLock() for writing */
3958 alock.leave();
3959
3960 /* try to remove from the list of known hard disks before performing actual
3961 * deletion (we favor the consistency of the media registry in the first
3962 * place which would have been broken if unregisterWithVirtualBox() failed
3963 * after we successfully deleted the storage) */
3964
3965 rc = unregisterWithVirtualBox();
3966
3967 alock.enter();
3968
3969 /* restore the state because we may fail below; we will set it later again*/
3970 m->state = MediumState_Created;
3971
3972 CheckComRCReturnRC(rc);
3973
3974 ComObjPtr <Progress> progress;
3975
3976 if (aProgress != NULL)
3977 {
3978 /* use the existing progress object... */
3979 progress = *aProgress;
3980
3981 /* ...but create a new one if it is null */
3982 if (progress.isNull())
3983 {
3984 progress.createObject();
3985 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
3986 BstrFmt(tr("Deleting hard disk storage unit '%ls'"),
3987 m->locationFull.raw()),
3988 FALSE /* aCancelable */);
3989 CheckComRCReturnRC(rc);
3990 }
3991 }
3992
3993 std::auto_ptr <Task> task(new Task(this, progress, Task::Delete));
3994 AssertComRCReturnRC(task->autoCaller.rc());
3995
3996 if (aWait)
3997 {
3998 /* go to Deleting state before starting the task */
3999 m->state = MediumState_Deleting;
4000
4001 rc = task->runNow();
4002 }
4003 else
4004 {
4005 rc = task->startThread();
4006 CheckComRCReturnRC(rc);
4007
4008 /* go to Deleting state before leaving the lock */
4009 m->state = MediumState_Deleting;
4010 }
4011
4012 /* task is now owned (or already deleted) by taskThread() so release it */
4013 task.release();
4014
4015 if (aProgress != NULL)
4016 {
4017 /* return progress to the caller */
4018 *aProgress = progress;
4019 }
4020
4021 return rc;
4022}
4023
4024/**
4025 * Creates a new differencing storage unit using the given target hard disk's
4026 * format and the location. Note that @c aTarget must be NotCreated.
4027 *
4028 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4029 * this hard disk for reading assuming that the caller has already done so. This
4030 * is used when taking an online snaopshot (where all original hard disks are
4031 * locked for writing and must remain such). Note however that if @a aWait is
4032 * @c false and this method returns a success then the thread started by
4033 * this method will unlock the hard disk (unless it is in
4034 * MediumState_LockedWrite state) so make sure the hard disk is either in
4035 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4036 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4037 * make sure you do it yourself as needed.
4038 *
4039 * If @a aProgress is not NULL but the object it points to is @c null then a new
4040 * progress object will be created and assigned to @a *aProgress on success,
4041 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4042 * progress object is created/used at all.
4043 *
4044 * When @a aWait is @c false, this method will create a thread to perform the
4045 * create operation asynchronously and will return immediately. Otherwise, it
4046 * will perform the operation on the calling thread and will not return to the
4047 * caller until the operation is completed. Note that @a aProgress cannot be
4048 * NULL when @a aWait is @c false (this method will assert in this case).
4049 *
4050 * @param aTarget Target hard disk.
4051 * @param aVariant Precise image variant to create.
4052 * @param aProgress Where to find/store a Progress object to track operation
4053 * completion.
4054 * @param aWait @c true if this method should block instead of creating
4055 * an asynchronous thread.
4056 *
4057 * @note Locks this object and @a aTarget for writing.
4058 */
4059HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4060 MediumVariant_T aVariant,
4061 ComObjPtr<Progress> *aProgress,
4062 bool aWait)
4063{
4064 AssertReturn(!aTarget.isNull(), E_FAIL);
4065 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4066
4067 AutoCaller autoCaller(this);
4068 CheckComRCReturnRC(autoCaller.rc());
4069
4070 AutoCaller targetCaller(aTarget);
4071 CheckComRCReturnRC(targetCaller.rc());
4072
4073 AutoMultiWriteLock2 alock(this, aTarget);
4074
4075 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4076
4077 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4078 AssertReturn(m->state == MediumState_LockedRead ||
4079 m->state == MediumState_LockedWrite, E_FAIL);
4080
4081 if (aTarget->m->state != MediumState_NotCreated)
4082 return aTarget->setStateError();
4083
4084 HRESULT rc = S_OK;
4085
4086 /* check that the hard disk is not attached to any VM in the current state*/
4087 for (BackRefList::const_iterator it = m->backRefs.begin();
4088 it != m->backRefs.end(); ++ it)
4089 {
4090 if (it->inCurState)
4091 {
4092 /* Note: when a VM snapshot is being taken, all normal hard disks
4093 * attached to the VM in the current state will be, as an exception,
4094 * also associated with the snapshot which is about to create (see
4095 * SnapshotMachine::init()) before deassociating them from the
4096 * current state (which takes place only on success in
4097 * Machine::fixupHardDisks()), so that the size of snapshotIds
4098 * will be 1 in this case. The given condition is used to filter out
4099 * this legal situatinon and do not report an error. */
4100
4101 if (it->snapshotIds.size() == 0)
4102 {
4103 return setError(VBOX_E_INVALID_OBJECT_STATE,
4104 tr("Hard disk '%ls' is attached to a virtual machine with UUID {%RTuuid}. No differencing hard disks based on it may be created until it is detached"),
4105 m->locationFull.raw(), it->machineId.raw());
4106 }
4107
4108 Assert(it->snapshotIds.size() == 1);
4109 }
4110 }
4111
4112 ComObjPtr <Progress> progress;
4113
4114 if (aProgress != NULL)
4115 {
4116 /* use the existing progress object... */
4117 progress = *aProgress;
4118
4119 /* ...but create a new one if it is null */
4120 if (progress.isNull())
4121 {
4122 progress.createObject();
4123 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4124 BstrFmt(tr("Creating differencing hard disk storage unit '%ls'"),
4125 aTarget->m->locationFull.raw()),
4126 TRUE /* aCancelable */);
4127 CheckComRCReturnRC(rc);
4128 }
4129 }
4130
4131 /* setup task object and thread to carry out the operation
4132 * asynchronously */
4133
4134 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateDiff));
4135 AssertComRCReturnRC(task->autoCaller.rc());
4136
4137 task->setData(aTarget);
4138 task->d.variant = aVariant;
4139
4140 /* register a task (it will deregister itself when done) */
4141 ++m->numCreateDiffTasks;
4142 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4143
4144 if (aWait)
4145 {
4146 /* go to Creating state before starting the task */
4147 aTarget->m->state = MediumState_Creating;
4148
4149 rc = task->runNow();
4150 }
4151 else
4152 {
4153 rc = task->startThread();
4154 CheckComRCReturnRC(rc);
4155
4156 /* go to Creating state before leaving the lock */
4157 aTarget->m->state = MediumState_Creating;
4158 }
4159
4160 /* task is now owned (or already deleted) by taskThread() so release it */
4161 task.release();
4162
4163 if (aProgress != NULL)
4164 {
4165 /* return progress to the caller */
4166 *aProgress = progress;
4167 }
4168
4169 return rc;
4170}
4171
4172/**
4173 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4174 * disks for the merge operation.
4175 *
4176 * This method is to be called prior to calling the #mergeTo() to perform
4177 * necessary consistency checks and place involved hard disks to appropriate
4178 * states. If #mergeTo() is not called or fails, the state modifications
4179 * performed by this method must be undone by #cancelMergeTo().
4180 *
4181 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4182 * responsibility to detach the source and all intermediate hard disks before
4183 * calling #mergeTo() (which will fail otherwise).
4184 *
4185 * See #mergeTo() for more information about merging.
4186 *
4187 * @param aTarget Target hard disk.
4188 * @param aChain Where to store the created merge chain.
4189 * @param aIgnoreAttachments Don't check if the source or any intermediate
4190 * hard disk is attached to any VM.
4191 *
4192 * @note Locks treeLock() for reading. Locks this object, aTarget and all
4193 * intermediate hard disks for writing.
4194 */
4195HRESULT Medium::prepareMergeTo(Medium *aTarget,
4196 MergeChain * &aChain,
4197 bool aIgnoreAttachments /*= false*/)
4198{
4199 AssertReturn(aTarget != NULL, E_FAIL);
4200
4201 AutoCaller autoCaller(this);
4202 AssertComRCReturnRC(autoCaller.rc());
4203
4204 AutoCaller targetCaller(aTarget);
4205 AssertComRCReturnRC(targetCaller.rc());
4206
4207 aChain = NULL;
4208
4209 /* we walk the tree */
4210 AutoReadLock treeLock(this->treeLock());
4211
4212 HRESULT rc = S_OK;
4213
4214 /* detect the merge direction */
4215 bool forward;
4216 {
4217 Medium *parent = mParent;
4218 while (parent != NULL && parent != aTarget)
4219 parent = parent->mParent;
4220 if (parent == aTarget)
4221 forward = false;
4222 else
4223 {
4224 parent = aTarget->mParent;
4225 while (parent != NULL && parent != this)
4226 parent = parent->mParent;
4227 if (parent == this)
4228 forward = true;
4229 else
4230 {
4231 Bstr tgtLoc;
4232 {
4233 AutoReadLock alock(this);
4234 tgtLoc = aTarget->locationFull();
4235 }
4236
4237 AutoReadLock alock(this);
4238 return setError(E_FAIL,
4239 tr("Hard disks '%ls' and '%ls' are unrelated"),
4240 m->locationFull.raw(), tgtLoc.raw());
4241 }
4242 }
4243 }
4244
4245 /* build the chain (will do necessary checks and state changes) */
4246 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4247 aIgnoreAttachments));
4248 {
4249 Medium *last = forward ? aTarget : this;
4250 Medium *first = forward ? this : aTarget;
4251
4252 for (;;)
4253 {
4254 if (last == aTarget)
4255 rc = chain->addTarget(last);
4256 else if (last == this)
4257 rc = chain->addSource(last);
4258 else
4259 rc = chain->addIntermediate(last);
4260 CheckComRCReturnRC(rc);
4261
4262 if (last == first)
4263 break;
4264
4265 last = last->mParent;
4266 }
4267 }
4268
4269 aChain = chain.release();
4270
4271 return S_OK;
4272}
4273
4274/**
4275 * Merges this hard disk to the specified hard disk which must be either its
4276 * direct ancestor or descendant.
4277 *
4278 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4279 * get two varians of the merge operation:
4280 *
4281 * forward merge
4282 * ------------------------->
4283 * [Extra] <- SOURCE <- Intermediate <- TARGET
4284 * Any Del Del LockWr
4285 *
4286 *
4287 * backward merge
4288 * <-------------------------
4289 * TARGET <- Intermediate <- SOURCE <- [Extra]
4290 * LockWr Del Del LockWr
4291 *
4292 * Each scheme shows the involved hard disks on the hard disk chain where
4293 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4294 * the hard disk must have at a time of the mergeTo() call.
4295 *
4296 * The hard disks in the square braces may be absent (e.g. when the forward
4297 * operation takes place and SOURCE is the base hard disk, or when the backward
4298 * merge operation takes place and TARGET is the last child in the chain) but if
4299 * they present they are involved too as shown.
4300 *
4301 * Nor the source hard disk neither intermediate hard disks may be attached to
4302 * any VM directly or in the snapshot, otherwise this method will assert.
4303 *
4304 * The #prepareMergeTo() method must be called prior to this method to place all
4305 * involved to necessary states and perform other consistency checks.
4306 *
4307 * If @a aWait is @c true then this method will perform the operation on the
4308 * calling thread and will not return to the caller until the operation is
4309 * completed. When this method succeeds, all intermediate hard disk objects in
4310 * the chain will be uninitialized, the state of the target hard disk (and all
4311 * involved extra hard disks) will be restored and @a aChain will be deleted.
4312 * Note that this (source) hard disk is not uninitialized because of possible
4313 * AutoCaller instances held by the caller of this method on the current thread.
4314 * It's therefore the responsibility of the caller to call Medium::uninit()
4315 * after releasing all callers in this case!
4316 *
4317 * If @a aWait is @c false then this method will crea,te a thread to perform the
4318 * create operation asynchronously and will return immediately. If the operation
4319 * succeeds, the thread will uninitialize the source hard disk object and all
4320 * intermediate hard disk objects in the chain, reset the state of the target
4321 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4322 * operation fails, the thread will only reset the states of all involved hard
4323 * disks and delete @a aChain.
4324 *
4325 * When this method fails (regardless of the @a aWait mode), it is a caller's
4326 * responsiblity to undo state changes and delete @a aChain using
4327 * #cancelMergeTo().
4328 *
4329 * If @a aProgress is not NULL but the object it points to is @c null then a new
4330 * progress object will be created and assigned to @a *aProgress on success,
4331 * otherwise the existing progress object is used. If Progress is NULL, then no
4332 * progress object is created/used at all. Note that @a aProgress cannot be
4333 * NULL when @a aWait is @c false (this method will assert in this case).
4334 *
4335 * @param aChain Merge chain created by #prepareMergeTo().
4336 * @param aProgress Where to find/store a Progress object to track operation
4337 * completion.
4338 * @param aWait @c true if this method should block instead of creating
4339 * an asynchronous thread.
4340 *
4341 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4342 * for writing.
4343 */
4344HRESULT Medium::mergeTo(MergeChain *aChain,
4345 ComObjPtr <Progress> *aProgress,
4346 bool aWait)
4347{
4348 AssertReturn(aChain != NULL, E_FAIL);
4349 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4350
4351 AutoCaller autoCaller(this);
4352 CheckComRCReturnRC(autoCaller.rc());
4353
4354 HRESULT rc = S_OK;
4355
4356 ComObjPtr <Progress> progress;
4357
4358 if (aProgress != NULL)
4359 {
4360 /* use the existing progress object... */
4361 progress = *aProgress;
4362
4363 /* ...but create a new one if it is null */
4364 if (progress.isNull())
4365 {
4366 AutoReadLock alock(this);
4367
4368 progress.createObject();
4369 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4370 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4371 name().raw(), aChain->target()->name().raw()),
4372 TRUE /* aCancelable */);
4373 CheckComRCReturnRC(rc);
4374 }
4375 }
4376
4377 /* setup task object and thread to carry out the operation
4378 * asynchronously */
4379
4380 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4381 AssertComRCReturnRC(task->autoCaller.rc());
4382
4383 task->setData(aChain);
4384
4385 /* Note: task owns aChain (will delete it when not needed) in all cases
4386 * except when @a aWait is @c true and runNow() fails -- in this case
4387 * aChain will be left away because cancelMergeTo() will be applied by the
4388 * caller on it as it is required in the documentation above */
4389
4390 if (aWait)
4391 {
4392 rc = task->runNow();
4393 }
4394 else
4395 {
4396 rc = task->startThread();
4397 CheckComRCReturnRC(rc);
4398 }
4399
4400 /* task is now owned (or already deleted) by taskThread() so release it */
4401 task.release();
4402
4403 if (aProgress != NULL)
4404 {
4405 /* return progress to the caller */
4406 *aProgress = progress;
4407 }
4408
4409 return rc;
4410}
4411
4412/**
4413 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4414 * or fails. Frees memory occupied by @a aChain.
4415 *
4416 * @param aChain Merge chain created by #prepareMergeTo().
4417 *
4418 * @note Locks the hard disks from the chain for writing.
4419 */
4420void Medium::cancelMergeTo(MergeChain *aChain)
4421{
4422 AutoCaller autoCaller(this);
4423 AssertComRCReturnVoid(autoCaller.rc());
4424
4425 AssertReturnVoid(aChain != NULL);
4426
4427 /* the destructor will do the thing */
4428 delete aChain;
4429}
4430
4431/**
4432 * Initializes the image medium object by opening an image file at the specified
4433 * location.
4434 *
4435 * @param aVirtualBox Parent VirtualBox object.
4436 * @param aLocation Path to the image file (can be relative to the
4437 * VirtualBox home directory).
4438 * @param aId UUID of the image.
4439 */
4440HRESULT Medium::protectedInit(VirtualBox *aVirtualBox, CBSTR aLocation,
4441 const Guid &aId)
4442{
4443 LogFlowThisFunc(("aLocation='%ls', aId={%RTuuid}\n", aLocation, aId.raw()));
4444
4445 AssertReturn(aVirtualBox, E_INVALIDARG);
4446 AssertReturn(aLocation, E_INVALIDARG);
4447 AssertReturn(!aId.isEmpty(), E_INVALIDARG);
4448
4449 /* Enclose the state transition NotReady->InInit->Ready */
4450 AutoInitSpan autoInitSpan(this);
4451 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4452
4453 HRESULT rc = S_OK;
4454
4455 /* share parent weakly */
4456 unconst(mVirtualBox) = aVirtualBox;
4457
4458 /* register with parent early, since uninit() will unconditionally
4459 * unregister on failure */
4460 mVirtualBox->addDependentChild(this);
4461
4462 /* there must be a storage unit */
4463 m->state = MediumState_Created;
4464
4465 unconst(m->id) = aId;
4466 rc = setLocation(aLocation);
4467 CheckComRCReturnRC(rc);
4468
4469 LogFlowThisFunc(("m->locationFull='%ls'\n", m->locationFull.raw()));
4470
4471 /* get all the information about the medium from the file */
4472 rc = queryInfo();
4473
4474 if (SUCCEEDED(rc))
4475 {
4476 /* if the image file is not accessible, it's not acceptable for the
4477 * newly opened media so convert this into an error */
4478 if (!m->lastAccessError.isEmpty())
4479 rc = setError(VBOX_E_FILE_ERROR, Utf8Str(m->lastAccessError).c_str());
4480 }
4481
4482 /* Confirm a successful initialization when it's the case */
4483 if (SUCCEEDED(rc))
4484 autoInitSpan.setSucceeded();
4485
4486 return rc;
4487}
4488
4489/**
4490 * Initializes the image medium object by loading its data from the given
4491 * settings node.
4492 *
4493 * Note that it is assumed that this method is called only for registered media.
4494 *
4495 * @param aVirtualBox Parent VirtualBox object.
4496 * @param aImageNode Either <DVDImage> or <FloppyImage> settings node.
4497 */
4498HRESULT Medium::protectedInit(VirtualBox *aVirtualBox,
4499 const settings::Medium &data)
4500{
4501 AssertReturn(aVirtualBox, E_INVALIDARG);
4502
4503 /* Enclose the state transition NotReady->InInit->Ready */
4504 AutoInitSpan autoInitSpan(this);
4505 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4506
4507 HRESULT rc = S_OK;
4508
4509 /* share parent weakly */
4510 unconst(mVirtualBox) = aVirtualBox;
4511
4512 /* register with parent early, since uninit() will unconditionally
4513 * unregister on failure */
4514 mVirtualBox->addDependentChild(this);
4515
4516 /* see below why we don't call queryInfo() (and therefore treat the medium
4517 * as inaccessible for now */
4518 m->state = MediumState_Inaccessible;
4519
4520 /* required */
4521 unconst(m->id) = data.uuid;
4522 /* required */
4523 rc = setLocation(data.strLocation);
4524 CheckComRCReturnRC(rc);
4525
4526 m->description = data.strDescription;
4527
4528 LogFlowThisFunc(("m->locationFull='%ls', m->id={%RTuuid}\n",
4529 m->locationFull.raw(), m->id.raw()));
4530
4531 /* Don't call queryInfo() for registered media to prevent the calling
4532 * thread (i.e. the VirtualBox server startup thread) from an unexpected
4533 * freeze but mark it as initially inaccessible instead. The vital UUID and
4534 * location properties are read from the registry file above; to get the
4535 * actual state and the rest of the data, the user will have to call
4536 * COMGETTER(State).*/
4537
4538 /* Confirm a successful initialization when it's the case */
4539 if (SUCCEEDED(rc))
4540 autoInitSpan.setSucceeded();
4541
4542 return rc;
4543}
4544
4545/**
4546 * Uninitializes the instance.
4547 *
4548 * Called either from FinalRelease() or by the parent when it gets destroyed.
4549 */
4550void Medium::protectedUninit()
4551{
4552 LogFlowThisFunc(("\n"));
4553
4554 /* Enclose the state transition Ready->InUninit->NotReady */
4555 AutoUninitSpan autoUninitSpan(this);
4556 if (autoUninitSpan.uninitDone())
4557 return;
4558
4559 mVirtualBox->removeDependentChild(this);
4560
4561 unconst(mVirtualBox).setNull();
4562}
4563
4564// private methods
4565////////////////////////////////////////////////////////////////////////////////
4566
4567/**
4568 * Checks that the format ID is valid and sets it on success.
4569 *
4570 * Note that this method will caller-reference the format object on success!
4571 * This reference must be released somewhere to let the MediumFormat object be
4572 * uninitialized.
4573 *
4574 * @note Must be called from under this object's write lock.
4575 */
4576HRESULT Medium::setFormat(CBSTR aFormat)
4577{
4578 /* get the format object first */
4579 {
4580 AutoReadLock propsLock(mVirtualBox->systemProperties());
4581
4582 unconst(m->formatObj)
4583 = mVirtualBox->systemProperties()->mediumFormat(aFormat);
4584 if (m->formatObj.isNull())
4585 return setError(E_INVALIDARG,
4586 tr("Invalid hard disk storage format '%ls'"), aFormat);
4587
4588 /* reference the format permanently to prevent its unexpected
4589 * uninitialization */
4590 HRESULT rc = m->formatObj->addCaller();
4591 AssertComRCReturnRC(rc);
4592
4593 /* get properties (preinsert them as keys in the map). Note that the
4594 * map doesn't grow over the object life time since the set of
4595 * properties is meant to be constant. */
4596
4597 Assert(m->properties.empty());
4598
4599 for (MediumFormat::PropertyList::const_iterator it =
4600 m->formatObj->properties().begin();
4601 it != m->formatObj->properties().end();
4602 ++ it)
4603 {
4604 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4605 }
4606 }
4607
4608 unconst(m->format) = aFormat;
4609
4610 return S_OK;
4611}
4612
4613/**
4614 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
4615 * write lock.
4616 *
4617 * @note Also reused by Medium::Reset().
4618 *
4619 * @note Locks treeLock() for reading.
4620 */
4621HRESULT Medium::canClose()
4622{
4623 /* we access children */
4624 AutoReadLock treeLock(this->treeLock());
4625
4626 if (children().size() != 0)
4627 return setError(E_FAIL,
4628 tr("Hard disk '%ls' has %d child hard disks"),
4629 children().size());
4630
4631 return S_OK;
4632}
4633
4634/**
4635 * @note Called from within this object's AutoWriteLock.
4636 */
4637HRESULT Medium::canAttach(const Guid & /* aMachineId */,
4638 const Guid & /* aSnapshotId */)
4639{
4640 if (m->numCreateDiffTasks > 0)
4641 return setError(E_FAIL,
4642 tr("One or more differencing child hard disks are being created for the hard disk '%ls' (%u)"),
4643 m->locationFull.raw(), m->numCreateDiffTasks);
4644
4645 return S_OK;
4646}
4647
4648/**
4649 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
4650 * from under mVirtualBox write lock.
4651 *
4652 * @note Locks treeLock() for writing.
4653 */
4654HRESULT Medium::unregisterWithVirtualBox()
4655{
4656 /* Note that we need to de-associate ourselves from the parent to let
4657 * unregisterHardDisk() properly save the registry */
4658
4659 /* we modify mParent and access children */
4660 AutoWriteLock treeLock(this->treeLock());
4661
4662 const ComObjPtr<Medium, ComWeakRef> parent = mParent;
4663
4664 AssertReturn(children().size() == 0, E_FAIL);
4665
4666 if (!mParent.isNull())
4667 {
4668 /* deassociate from the parent, associate with VirtualBox */
4669 mVirtualBox->addDependentChild(this);
4670 mParent->removeDependentChild(this);
4671 mParent.setNull();
4672 }
4673
4674 HRESULT rc = mVirtualBox->unregisterHardDisk(this);
4675
4676 if (FAILED(rc))
4677 {
4678 if (!parent.isNull())
4679 {
4680 /* re-associate with the parent as we are still relatives in the
4681 * registry */
4682 mParent = parent;
4683 mParent->addDependentChild(this);
4684 mVirtualBox->removeDependentChild(this);
4685 }
4686 }
4687
4688 return rc;
4689}
4690
4691/**
4692 * Returns the last error message collected by the vdErrorCall callback and
4693 * resets it.
4694 *
4695 * The error message is returned prepended with a dot and a space, like this:
4696 * <code>
4697 * ". <error_text> (%Rrc)"
4698 * </code>
4699 * to make it easily appendable to a more general error message. The @c %Rrc
4700 * format string is given @a aVRC as an argument.
4701 *
4702 * If there is no last error message collected by vdErrorCall or if it is a
4703 * null or empty string, then this function returns the following text:
4704 * <code>
4705 * " (%Rrc)"
4706 * </code>
4707 *
4708 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4709 * the callback isn't called by more than one thread at a time.
4710 *
4711 * @param aVRC VBox error code to use when no error message is provided.
4712 */
4713Utf8Str Medium::vdError(int aVRC)
4714{
4715 Utf8Str error;
4716
4717 if (m->vdError.isEmpty())
4718 error = Utf8StrFmt(" (%Rrc)", aVRC);
4719 else
4720 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4721
4722 m->vdError.setNull();
4723
4724 return error;
4725}
4726
4727/**
4728 * Error message callback.
4729 *
4730 * Puts the reported error message to the m->vdError field.
4731 *
4732 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4733 * the callback isn't called by more than one thread at a time.
4734 *
4735 * @param pvUser The opaque data passed on container creation.
4736 * @param rc The VBox error code.
4737 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4738 * @param pszFormat Error message format string.
4739 * @param va Error message arguments.
4740 */
4741/*static*/
4742DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4743 const char *pszFormat, va_list va)
4744{
4745 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4746
4747 Medium *that = static_cast<Medium*>(pvUser);
4748 AssertReturnVoid(that != NULL);
4749
4750 if (that->m->vdError.isEmpty())
4751 that->m->vdError =
4752 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4753 else
4754 that->m->vdError =
4755 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4756 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4757}
4758
4759/**
4760 * PFNVMPROGRESS callback handler for Task operations.
4761 *
4762 * @param uPercent Completetion precentage (0-100).
4763 * @param pvUser Pointer to the Progress instance.
4764 */
4765/*static*/
4766DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4767 void *pvUser)
4768{
4769 Medium *that = static_cast<Medium*>(pvUser);
4770 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4771
4772 if (that->m->vdProgress != NULL)
4773 {
4774 /* update the progress object, capping it at 99% as the final percent
4775 * is used for additional operations like setting the UUIDs and similar. */
4776 HRESULT rc = that->m->vdProgress->SetCurrentOperationProgress(uPercent * 99 / 100);
4777 if (FAILED(rc))
4778 {
4779 if (rc == E_FAIL)
4780 return VERR_CANCELLED;
4781 else
4782 return VERR_INVALID_STATE;
4783 }
4784 }
4785
4786 return VINF_SUCCESS;
4787}
4788
4789/* static */
4790DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4791 const char * /* pszzValid */)
4792{
4793 Medium *that = static_cast<Medium*>(pvUser);
4794 AssertReturn(that != NULL, false);
4795
4796 /* we always return true since the only keys we have are those found in
4797 * VDBACKENDINFO */
4798 return true;
4799}
4800
4801/* static */
4802DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4803 size_t *pcbValue)
4804{
4805 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4806
4807 Medium *that = static_cast<Medium*>(pvUser);
4808 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4809
4810 Data::PropertyMap::const_iterator it =
4811 that->m->properties.find(Bstr(pszName));
4812 if (it == that->m->properties.end())
4813 return VERR_CFGM_VALUE_NOT_FOUND;
4814
4815 /* we interpret null values as "no value" in Medium */
4816 if (it->second.isNull())
4817 return VERR_CFGM_VALUE_NOT_FOUND;
4818
4819 *pcbValue = it->second.length() + 1 /* include terminator */;
4820
4821 return VINF_SUCCESS;
4822}
4823
4824/* static */
4825DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4826 char *pszValue, size_t cchValue)
4827{
4828 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4829
4830 Medium *that = static_cast<Medium*>(pvUser);
4831 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4832
4833 Data::PropertyMap::const_iterator it =
4834 that->m->properties.find(Bstr(pszName));
4835 if (it == that->m->properties.end())
4836 return VERR_CFGM_VALUE_NOT_FOUND;
4837
4838 Utf8Str value = it->second;
4839 if (value.length() >= cchValue)
4840 return VERR_CFGM_NOT_ENOUGH_SPACE;
4841
4842 /* we interpret null values as "no value" in Medium */
4843 if (it->second.isNull())
4844 return VERR_CFGM_VALUE_NOT_FOUND;
4845
4846 memcpy(pszValue, value.c_str(), value.length() + 1);
4847
4848 return VINF_SUCCESS;
4849}
4850
4851/**
4852 * Thread function for time-consuming tasks.
4853 *
4854 * The Task structure passed to @a pvUser must be allocated using new and will
4855 * be freed by this method before it returns.
4856 *
4857 * @param pvUser Pointer to the Task instance.
4858 */
4859/* static */
4860DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
4861{
4862 std::auto_ptr <Task> task(static_cast <Task *>(pvUser));
4863 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
4864
4865 bool isAsync = thread != NIL_RTTHREAD;
4866
4867 Medium *that = task->that;
4868
4869 /// @todo ugly hack, fix ComAssert... later
4870 #define setError that->setError
4871
4872 /* Note: no need in AutoCaller because Task does that */
4873
4874 LogFlowFuncEnter();
4875 LogFlowFunc(("{%p}: operation=%d\n", that, task->operation));
4876
4877 HRESULT rc = S_OK;
4878
4879 switch (task->operation)
4880 {
4881 ////////////////////////////////////////////////////////////////////////
4882
4883 case Task::CreateBase:
4884 {
4885 /* The lock is also used as a signal from the task initiator (which
4886 * releases it only after RTThreadCreate()) that we can start the job */
4887 AutoWriteLock thatLock(that);
4888
4889 /* these parameters we need after creation */
4890 uint64_t size = 0, logicalSize = 0;
4891
4892 /* The object may request a specific UUID (through a special form of
4893 * the setLocation() argument). Otherwise we have to generate it */
4894 Guid id = that->m->id;
4895 bool generateUuid = id.isEmpty();
4896 if (generateUuid)
4897 {
4898 id.create();
4899 /* VirtualBox::registerHardDisk() will need UUID */
4900 unconst(that->m->id) = id;
4901 }
4902
4903 try
4904 {
4905 PVBOXHDD hdd;
4906 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
4907 ComAssertRCThrow(vrc, E_FAIL);
4908
4909 Utf8Str format(that->m->format);
4910 Utf8Str location(that->m->locationFull);
4911 /* uint64_t capabilities = */ that->m->formatObj->capabilities();
4912
4913 /* unlock before the potentially lengthy operation */
4914 Assert(that->m->state == MediumState_Creating);
4915 thatLock.leave();
4916
4917 try
4918 {
4919 /* ensure the directory exists */
4920 rc = VirtualBox::ensureFilePathExists(location);
4921 CheckComRCThrowRC(rc);
4922
4923 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
4924
4925 /* needed for vdProgressCallback */
4926 that->m->vdProgress = task->progress;
4927
4928 vrc = VDCreateBase(hdd, format.c_str(), location.c_str(),
4929 task->d.size * _1M,
4930 task->d.variant,
4931 NULL, &geo, &geo, id.raw(),
4932 VD_OPEN_FLAGS_NORMAL,
4933 NULL, that->m->vdDiskIfaces);
4934
4935 if (RT_FAILURE(vrc))
4936 {
4937 throw setError(E_FAIL,
4938 tr("Could not create the hard disk storage unit '%s'%s"),
4939 location.raw(), that->vdError(vrc).raw());
4940 }
4941
4942 size = VDGetFileSize(hdd, 0);
4943 logicalSize = VDGetSize(hdd, 0) / _1M;
4944 }
4945 catch (HRESULT aRC) { rc = aRC; }
4946
4947 VDDestroy(hdd);
4948 }
4949 catch (HRESULT aRC) { rc = aRC; }
4950
4951 if (SUCCEEDED(rc))
4952 {
4953 /* register with mVirtualBox as the last step and move to
4954 * Created state only on success (leaving an orphan file is
4955 * better than breaking media registry consistency) */
4956 rc = that->mVirtualBox->registerHardDisk(that);
4957 }
4958
4959 thatLock.maybeEnter();
4960
4961 if (SUCCEEDED(rc))
4962 {
4963 that->m->state = MediumState_Created;
4964
4965 that->m->size = size;
4966 that->m->logicalSize = logicalSize;
4967 }
4968 else
4969 {
4970 /* back to NotCreated on failure */
4971 that->m->state = MediumState_NotCreated;
4972
4973 /* reset UUID to prevent it from being reused next time */
4974 if (generateUuid)
4975 unconst(that->m->id).clear();
4976 }
4977
4978 break;
4979 }
4980
4981 ////////////////////////////////////////////////////////////////////////
4982
4983 case Task::CreateDiff:
4984 {
4985 ComObjPtr<Medium> &target = task->d.target;
4986
4987 /* Lock both in {parent,child} order. The lock is also used as a
4988 * signal from the task initiator (which releases it only after
4989 * RTThreadCreate()) that we can start the job*/
4990 AutoMultiWriteLock2 thatLock(that, target);
4991
4992 uint64_t size = 0, logicalSize = 0;
4993
4994 /* The object may request a specific UUID (through a special form of
4995 * the setLocation() argument). Otherwise we have to generate it */
4996 Guid targetId = target->m->id;
4997 bool generateUuid = targetId.isEmpty();
4998 if (generateUuid)
4999 {
5000 targetId.create();
5001 /* VirtualBox::registerHardDisk() will need UUID */
5002 unconst(target->m->id) = targetId;
5003 }
5004
5005 try
5006 {
5007 PVBOXHDD hdd;
5008 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5009 ComAssertRCThrow(vrc, E_FAIL);
5010
5011 Guid id = that->m->id;
5012 Utf8Str format(that->m->format);
5013 Utf8Str location(that->m->locationFull);
5014
5015 Utf8Str targetFormat(target->m->format);
5016 Utf8Str targetLocation(target->m->locationFull);
5017
5018 Assert(target->m->state == MediumState_Creating);
5019
5020 /* Note: MediumState_LockedWrite is ok when taking an online
5021 * snapshot */
5022 Assert(that->m->state == MediumState_LockedRead ||
5023 that->m->state == MediumState_LockedWrite);
5024
5025 /* unlock before the potentially lengthy operation */
5026 thatLock.leave();
5027
5028 try
5029 {
5030 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5031 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5032 that->m->vdDiskIfaces);
5033 if (RT_FAILURE(vrc))
5034 {
5035 throw setError(E_FAIL,
5036 tr("Could not open the hard disk storage unit '%s'%s"),
5037 location.raw(), that->vdError(vrc).raw());
5038 }
5039
5040 /* ensure the target directory exists */
5041 rc = VirtualBox::ensureFilePathExists(targetLocation);
5042 CheckComRCThrowRC(rc);
5043
5044 /* needed for vdProgressCallback */
5045 that->m->vdProgress = task->progress;
5046
5047 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5048 * be on the safe side. */
5049 vrc = VDCreateDiff(hdd, targetFormat.c_str(),
5050 targetLocation.c_str(),
5051 task->d.variant,
5052 NULL, targetId.raw(),
5053 id.raw(),
5054 VD_OPEN_FLAGS_NORMAL,
5055 target->m->vdDiskIfaces,
5056 that->m->vdDiskIfaces);
5057
5058 that->m->vdProgress = NULL;
5059
5060 if (RT_FAILURE(vrc))
5061 {
5062 throw setError(E_FAIL,
5063 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5064 targetLocation.raw(), that->vdError(vrc).raw());
5065 }
5066
5067 size = VDGetFileSize(hdd, 1);
5068 logicalSize = VDGetSize(hdd, 1) / _1M;
5069 }
5070 catch (HRESULT aRC) { rc = aRC; }
5071
5072 VDDestroy(hdd);
5073 }
5074 catch (HRESULT aRC) { rc = aRC; }
5075
5076 if (SUCCEEDED(rc))
5077 {
5078 /* we set mParent & children() (note that thatLock is released
5079 * here), but lock VirtualBox first to follow the rule */
5080 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5081 that->treeLock());
5082
5083 Assert(target->mParent.isNull());
5084
5085 /* associate the child with the parent and deassociate from
5086 * VirtualBox */
5087 target->mParent = that;
5088 that->addDependentChild(target);
5089 target->mVirtualBox->removeDependentChild(target);
5090
5091 /* diffs for immutable hard disks are auto-reset by default */
5092 target->m->autoReset =
5093 that->base()->m->type == MediumType_Immutable ?
5094 TRUE : FALSE;
5095
5096 /* register with mVirtualBox as the last step and move to
5097 * Created state only on success (leaving an orphan file is
5098 * better than breaking media registry consistency) */
5099 rc = that->mVirtualBox->registerHardDisk(target);
5100
5101 if (FAILED(rc))
5102 {
5103 /* break the parent association on failure to register */
5104 target->mVirtualBox->addDependentChild(target);
5105 that->removeDependentChild(target);
5106 target->mParent.setNull();
5107 }
5108 }
5109
5110 thatLock.maybeEnter();
5111
5112 if (SUCCEEDED(rc))
5113 {
5114 target->m->state = MediumState_Created;
5115
5116 target->m->size = size;
5117 target->m->logicalSize = logicalSize;
5118 }
5119 else
5120 {
5121 /* back to NotCreated on failure */
5122 target->m->state = MediumState_NotCreated;
5123
5124 target->m->autoReset = FALSE;
5125
5126 /* reset UUID to prevent it from being reused next time */
5127 if (generateUuid)
5128 unconst(target->m->id).clear();
5129 }
5130
5131 if (isAsync)
5132 {
5133 /* unlock ourselves when done (unless in MediumState_LockedWrite
5134 * state because of taking the online snapshot*/
5135 if (that->m->state != MediumState_LockedWrite)
5136 {
5137 HRESULT rc2 = that->UnlockRead(NULL);
5138 AssertComRC(rc2);
5139 }
5140 }
5141
5142 /* deregister the task registered in createDiffStorage() */
5143 Assert(that->m->numCreateDiffTasks != 0);
5144 --that->m->numCreateDiffTasks;
5145
5146 /* Note that in sync mode, it's the caller's responsibility to
5147 * unlock the hard disk */
5148
5149 break;
5150 }
5151
5152 ////////////////////////////////////////////////////////////////////////
5153
5154 case Task::Merge:
5155 {
5156 /* The lock is also used as a signal from the task initiator (which
5157 * releases it only after RTThreadCreate()) that we can start the
5158 * job. We don't actually need the lock for anything else since the
5159 * object is protected by MediumState_Deleting and we don't modify
5160 * its sensitive fields below */
5161 {
5162 AutoWriteLock thatLock(that);
5163 }
5164
5165 MergeChain *chain = task->d.chain.get();
5166
5167#if 0
5168 LogFlow(("*** MERGE forward = %RTbool\n", chain->isForward()));
5169#endif
5170
5171 try
5172 {
5173 PVBOXHDD hdd;
5174 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5175 ComAssertRCThrow(vrc, E_FAIL);
5176
5177 try
5178 {
5179 /* Open all hard disks in the chain (they are in the
5180 * {parent,child} order in there. Note that we don't lock
5181 * objects in this chain since they must be in states
5182 * (Deleting and LockedWrite) that prevent from changing
5183 * their format and location fields from outside. */
5184
5185 for (MergeChain::const_iterator it = chain->begin();
5186 it != chain->end(); ++ it)
5187 {
5188 /* complex sanity (sane complexity) */
5189 Assert((chain->isForward() &&
5190 ((*it != chain->back() &&
5191 (*it)->m->state == MediumState_Deleting) ||
5192 (*it == chain->back() &&
5193 (*it)->m->state == MediumState_LockedWrite))) ||
5194 (!chain->isForward() &&
5195 ((*it != chain->front() &&
5196 (*it)->m->state == MediumState_Deleting) ||
5197 (*it == chain->front() &&
5198 (*it)->m->state == MediumState_LockedWrite))));
5199
5200 Assert(*it == chain->target() ||
5201 (*it)->m->backRefs.size() == 0);
5202
5203 /* open the first image with VDOPEN_FLAGS_INFO because
5204 * it's not necessarily the base one */
5205 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5206 Utf8Str((*it)->m->locationFull).c_str(),
5207 it == chain->begin() ?
5208 VD_OPEN_FLAGS_INFO : 0,
5209 (*it)->m->vdDiskIfaces);
5210 if (RT_FAILURE(vrc))
5211 throw vrc;
5212#if 0
5213 LogFlow(("*** MERGE disk = %ls\n", (*it)->m->locationFull.raw()));
5214#endif
5215 }
5216
5217 /* needed for vdProgressCallback */
5218 that->m->vdProgress = task->progress;
5219
5220 unsigned start = chain->isForward() ?
5221 0 : (unsigned)chain->size() - 1;
5222 unsigned end = chain->isForward() ?
5223 (unsigned)chain->size() - 1 : 0;
5224#if 0
5225 LogFlow(("*** MERGE from %d to %d\n", start, end));
5226#endif
5227 vrc = VDMerge(hdd, start, end, that->m->vdDiskIfaces);
5228
5229 that->m->vdProgress = NULL;
5230
5231 if (RT_FAILURE(vrc))
5232 throw vrc;
5233
5234 /* update parent UUIDs */
5235 /// @todo VDMerge should be taught to do so, including the
5236 /// multiple children case
5237 if (chain->isForward())
5238 {
5239 /* target's UUID needs to be updated (note that target
5240 * is the only image in the container on success) */
5241 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5242 if (RT_FAILURE(vrc))
5243 throw vrc;
5244 }
5245 else
5246 {
5247 /* we need to update UUIDs of all source's children
5248 * which cannot be part of the container at once so
5249 * add each one in there individually */
5250 if (chain->children().size() > 0)
5251 {
5252 for (List::const_iterator it = chain->children().begin();
5253 it != chain->children().end(); ++ it)
5254 {
5255 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5256 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5257 Utf8Str((*it)->m->locationFull).c_str(),
5258 VD_OPEN_FLAGS_INFO,
5259 (*it)->m->vdDiskIfaces);
5260 if (RT_FAILURE(vrc))
5261 throw vrc;
5262
5263 vrc = VDSetParentUuid(hdd, 1,
5264 chain->target()->m->id);
5265 if (RT_FAILURE(vrc))
5266 throw vrc;
5267
5268 vrc = VDClose(hdd, false /* fDelete */);
5269 if (RT_FAILURE(vrc))
5270 throw vrc;
5271 }
5272 }
5273 }
5274 }
5275 catch (HRESULT aRC) { rc = aRC; }
5276 catch (int aVRC)
5277 {
5278 throw setError(E_FAIL,
5279 tr("Could not merge the hard disk '%ls' to '%ls'%s"),
5280 chain->source()->m->locationFull.raw(),
5281 chain->target()->m->locationFull.raw(),
5282 that->vdError(aVRC).raw());
5283 }
5284
5285 VDDestroy(hdd);
5286 }
5287 catch (HRESULT aRC) { rc = aRC; }
5288
5289 HRESULT rc2;
5290
5291 bool saveSettingsFailed = false;
5292
5293 if (SUCCEEDED(rc))
5294 {
5295 /* all hard disks but the target were successfully deleted by
5296 * VDMerge; reparent the last one and uninitialize deleted */
5297
5298 /* we set mParent & children() (note that thatLock is released
5299 * here), but lock VirtualBox first to follow the rule */
5300 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5301 that->treeLock());
5302
5303 Medium *source = chain->source();
5304 Medium *target = chain->target();
5305
5306 if (chain->isForward())
5307 {
5308 /* first, unregister the target since it may become a base
5309 * hard disk which needs re-registration */
5310 rc2 = target->mVirtualBox->
5311 unregisterHardDisk(target, false /* aSaveSettings */);
5312 AssertComRC(rc2);
5313
5314 /* then, reparent it and disconnect the deleted branch at
5315 * both ends (chain->parent() is source's parent) */
5316 target->mParent->removeDependentChild(target);
5317 target->mParent = chain->parent();
5318 if (!target->mParent.isNull())
5319 {
5320 target->mParent->addDependentChild(target);
5321 target->mParent->removeDependentChild(source);
5322 source->mParent.setNull();
5323 }
5324 else
5325 {
5326 target->mVirtualBox->addDependentChild(target);
5327 target->mVirtualBox->removeDependentChild(source);
5328 }
5329
5330 /* then, register again */
5331 rc2 = target->mVirtualBox->
5332 registerHardDisk(target, false /* aSaveSettings */);
5333 AssertComRC(rc2);
5334 }
5335 else
5336 {
5337 Assert(target->children().size() == 1);
5338 Medium *targetChild = target->children().front();
5339
5340 /* disconnect the deleted branch at the elder end */
5341 target->removeDependentChild(targetChild);
5342 targetChild->mParent.setNull();
5343
5344 const List &children = chain->children();
5345
5346 /* reparent source's chidren and disconnect the deleted
5347 * branch at the younger end m*/
5348 if (children.size() > 0)
5349 {
5350 /* obey {parent,child} lock order */
5351 AutoWriteLock sourceLock(source);
5352
5353 for (List::const_iterator it = children.begin();
5354 it != children.end(); ++ it)
5355 {
5356 AutoWriteLock childLock(*it);
5357
5358 (*it)->mParent = target;
5359 (*it)->mParent->addDependentChild(*it);
5360 source->removeDependentChild(*it);
5361 }
5362 }
5363 }
5364
5365 /* try to save the hard disk registry */
5366 rc = that->mVirtualBox->saveSettings();
5367
5368 if (SUCCEEDED(rc))
5369 {
5370 /* unregister and uninitialize all hard disks in the chain
5371 * but the target */
5372
5373 for (MergeChain::iterator it = chain->begin();
5374 it != chain->end();)
5375 {
5376 if (*it == chain->target())
5377 {
5378 ++ it;
5379 continue;
5380 }
5381
5382 rc2 = (*it)->mVirtualBox->
5383 unregisterHardDisk(*it, false /* aSaveSettings */);
5384 AssertComRC(rc2);
5385
5386 /* now, uninitialize the deleted hard disk (note that
5387 * due to the Deleting state, uninit() will not touch
5388 * the parent-child relationship so we need to
5389 * uninitialize each disk individually) */
5390
5391 /* note that the operation initiator hard disk (which is
5392 * normally also the source hard disk) is a special case
5393 * -- there is one more caller added by Task to it which
5394 * we must release. Also, if we are in sync mode, the
5395 * caller may still hold an AutoCaller instance for it
5396 * and therefore we cannot uninit() it (it's therefore
5397 * the caller's responsibility) */
5398 if (*it == that)
5399 task->autoCaller.release();
5400
5401 /* release the caller added by MergeChain before
5402 * uninit() */
5403 (*it)->releaseCaller();
5404
5405 if (isAsync || *it != that)
5406 (*it)->uninit();
5407
5408 /* delete (to prevent uninitialization in MergeChain
5409 * dtor) and advance to the next item */
5410 it = chain->erase(it);
5411 }
5412
5413 /* Note that states of all other hard disks (target, parent,
5414 * children) will be restored by the MergeChain dtor */
5415 }
5416 else
5417 {
5418 /* too bad if we fail, but we'll need to rollback everything
5419 * we did above to at least keep the HD tree in sync with
5420 * the current registry on disk */
5421
5422 saveSettingsFailed = true;
5423
5424 /// @todo NEWMEDIA implement a proper undo
5425
5426 AssertFailed();
5427 }
5428 }
5429
5430 if (FAILED(rc))
5431 {
5432 /* Here we come if either VDMerge() failed (in which case we
5433 * assume that it tried to do everything to make a further
5434 * retry possible -- e.g. not deleted intermediate hard disks
5435 * and so on) or VirtualBox::saveSettings() failed (where we
5436 * should have the original tree but with intermediate storage
5437 * units deleted by VDMerge()). We have to only restore states
5438 * (through the MergeChain dtor) unless we are run synchronously
5439 * in which case it's the responsibility of the caller as stated
5440 * in the mergeTo() docs. The latter also implies that we
5441 * don't own the merge chain, so release it in this case. */
5442
5443 if (!isAsync)
5444 task->d.chain.release();
5445
5446 NOREF(saveSettingsFailed);
5447 }
5448
5449 break;
5450 }
5451
5452 ////////////////////////////////////////////////////////////////////////
5453
5454 case Task::Clone:
5455 {
5456 ComObjPtr<Medium> &target = task->d.target;
5457 ComObjPtr<Medium> &parent = task->d.parentDisk;
5458
5459 /* Lock all in {parent,child} order. The lock is also used as a
5460 * signal from the task initiator (which releases it only after
5461 * RTThreadCreate()) that we can start the job. */
5462 AutoMultiWriteLock3 thatLock(that, target, parent);
5463
5464 ImageChain *srcChain = task->d.source.get();
5465 ImageChain *parentChain = task->d.parent.get();
5466
5467 uint64_t size = 0, logicalSize = 0;
5468
5469 /* The object may request a specific UUID (through a special form of
5470 * the setLocation() argument). Otherwise we have to generate it */
5471 Guid targetId = target->m->id;
5472 bool generateUuid = targetId.isEmpty();
5473 if (generateUuid)
5474 {
5475 targetId.create();
5476 /* VirtualBox::registerHardDisk() will need UUID */
5477 unconst(target->m->id) = targetId;
5478 }
5479
5480 try
5481 {
5482 PVBOXHDD hdd;
5483 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5484 ComAssertRCThrow(vrc, E_FAIL);
5485
5486 try
5487 {
5488 /* Open all hard disk images in the source chain. */
5489 for (List::const_iterator it = srcChain->begin();
5490 it != srcChain->end(); ++ it)
5491 {
5492 /* sanity check */
5493 Assert((*it)->m->state == MediumState_LockedRead);
5494
5495 /** Open all images in read-only mode. */
5496 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5497 Utf8Str((*it)->m->locationFull).c_str(),
5498 VD_OPEN_FLAGS_READONLY,
5499 (*it)->m->vdDiskIfaces);
5500 if (RT_FAILURE(vrc))
5501 {
5502 throw setError(E_FAIL,
5503 tr("Could not open the hard disk storage unit '%s'%s"),
5504 Utf8Str((*it)->m->locationFull).raw(),
5505 that->vdError(vrc).raw());
5506 }
5507 }
5508
5509 /* unlock before the potentially lengthy operation */
5510 thatLock.leave();
5511
5512 Utf8Str targetFormat(target->m->format);
5513 Utf8Str targetLocation(target->m->locationFull);
5514
5515 Assert( target->m->state == MediumState_Creating
5516 || target->m->state == MediumState_LockedWrite);
5517 Assert(that->m->state == MediumState_LockedRead);
5518 Assert(parent.isNull() || parent->m->state == MediumState_LockedRead);
5519
5520 /* ensure the target directory exists */
5521 rc = VirtualBox::ensureFilePathExists(targetLocation);
5522 CheckComRCThrowRC(rc);
5523
5524 /* needed for vdProgressCallback */
5525 that->m->vdProgress = task->progress;
5526
5527 PVBOXHDD targetHdd;
5528 int vrc = VDCreate(that->m->vdDiskIfaces, &targetHdd);
5529 ComAssertRCThrow(vrc, E_FAIL);
5530
5531 try
5532 {
5533 /* Open all hard disk images in the parent chain. */
5534 for (List::const_iterator it = parentChain->begin();
5535 it != parentChain->end(); ++ it)
5536 {
5537 /* sanity check */
5538 Assert( (*it)->m->state == MediumState_LockedRead
5539 || (*it)->m->state == MediumState_LockedWrite);
5540
5541 /* Open all images in appropriate mode. */
5542 vrc = VDOpen(targetHdd, Utf8Str((*it)->m->format).c_str(),
5543 Utf8Str((*it)->m->locationFull).c_str(),
5544 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5545 (*it)->m->vdDiskIfaces);
5546 if (RT_FAILURE(vrc))
5547 {
5548 throw setError(E_FAIL,
5549 tr("Could not open the hard disk storage unit '%s'%s"),
5550 Utf8Str((*it)->m->locationFull).raw(),
5551 that->vdError(vrc).raw());
5552 }
5553 }
5554
5555 vrc = VDCopy(hdd, VD_LAST_IMAGE, targetHdd,
5556 targetFormat.c_str(),
5557 target->m->state == MediumState_Creating ? targetLocation.raw() : (char *)NULL,
5558 false, 0,
5559 task->d.variant, targetId.raw(), NULL,
5560 target->m->vdDiskIfaces,
5561 that->m->vdDiskIfaces);
5562
5563 that->m->vdProgress = NULL;
5564
5565 if (RT_FAILURE(vrc))
5566 {
5567 throw setError(E_FAIL,
5568 tr("Could not create the clone hard disk '%s'%s"),
5569 targetLocation.raw(), that->vdError(vrc).raw());
5570 }
5571 size = VDGetFileSize(targetHdd, 0);
5572 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5573 }
5574 catch (HRESULT aRC) { rc = aRC; }
5575
5576 VDDestroy(targetHdd);
5577 }
5578 catch (HRESULT aRC) { rc = aRC; }
5579
5580 VDDestroy(hdd);
5581 }
5582 catch (HRESULT aRC) { rc = aRC; }
5583
5584 /* Only do the parent changes for newly created images. */
5585 if (target->m->state == MediumState_Creating)
5586 {
5587 if (SUCCEEDED(rc))
5588 {
5589 /* we set mParent & children() (note that thatLock is released
5590 * here), but lock VirtualBox first to follow the rule */
5591 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5592 that->treeLock());
5593
5594 Assert(target->mParent.isNull());
5595
5596 if (parent)
5597 {
5598 /* associate the clone with the parent and deassociate
5599 * from VirtualBox */
5600 target->mParent = parent;
5601 parent->addDependentChild(target);
5602 target->mVirtualBox->removeDependentChild(target);
5603
5604 /* register with mVirtualBox as the last step and move to
5605 * Created state only on success (leaving an orphan file is
5606 * better than breaking media registry consistency) */
5607 rc = parent->mVirtualBox->registerHardDisk(target);
5608
5609 if (FAILED(rc))
5610 {
5611 /* break parent association on failure to register */
5612 target->mVirtualBox->addDependentChild(target);
5613 parent->removeDependentChild(target);
5614 target->mParent.setNull();
5615 }
5616 }
5617 else
5618 {
5619 /* just register */
5620 rc = that->mVirtualBox->registerHardDisk(target);
5621 }
5622 }
5623 }
5624
5625 thatLock.maybeEnter();
5626
5627 if (target->m->state == MediumState_Creating)
5628 {
5629 if (SUCCEEDED(rc))
5630 {
5631 target->m->state = MediumState_Created;
5632
5633 target->m->size = size;
5634 target->m->logicalSize = logicalSize;
5635 }
5636 else
5637 {
5638 /* back to NotCreated on failure */
5639 target->m->state = MediumState_NotCreated;
5640
5641 /* reset UUID to prevent it from being reused next time */
5642 if (generateUuid)
5643 unconst(target->m->id).clear();
5644 }
5645 }
5646
5647 /* Everything is explicitly unlocked when the task exits,
5648 * as the task destruction also destroys the source chain. */
5649
5650 /* Make sure the source chain is released early. It could happen
5651 * that we get a deadlock in Appliance::Import when Medium::Close
5652 * is called & the source chain is released at the same time. */
5653 task->d.source.reset();
5654 break;
5655 }
5656
5657 ////////////////////////////////////////////////////////////////////////
5658
5659 case Task::Delete:
5660 {
5661 /* The lock is also used as a signal from the task initiator (which
5662 * releases it only after RTThreadCreate()) that we can start the job */
5663 AutoWriteLock thatLock(that);
5664
5665 try
5666 {
5667 PVBOXHDD hdd;
5668 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5669 ComAssertRCThrow(vrc, E_FAIL);
5670
5671 Utf8Str format(that->m->format);
5672 Utf8Str location(that->m->locationFull);
5673
5674 /* unlock before the potentially lengthy operation */
5675 Assert(that->m->state == MediumState_Deleting);
5676 thatLock.leave();
5677
5678 try
5679 {
5680 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5681 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5682 that->m->vdDiskIfaces);
5683 if (RT_SUCCESS(vrc))
5684 vrc = VDClose(hdd, true /* fDelete */);
5685
5686 if (RT_FAILURE(vrc))
5687 {
5688 throw setError(E_FAIL,
5689 tr("Could not delete the hard disk storage unit '%s'%s"),
5690 location.raw(), that->vdError(vrc).raw());
5691 }
5692
5693 }
5694 catch (HRESULT aRC) { rc = aRC; }
5695
5696 VDDestroy(hdd);
5697 }
5698 catch (HRESULT aRC) { rc = aRC; }
5699
5700 thatLock.maybeEnter();
5701
5702 /* go to the NotCreated state even on failure since the storage
5703 * may have been already partially deleted and cannot be used any
5704 * more. One will be able to manually re-open the storage if really
5705 * needed to re-register it. */
5706 that->m->state = MediumState_NotCreated;
5707
5708 /* Reset UUID to prevent Create* from reusing it again */
5709 unconst(that->m->id).clear();
5710
5711 break;
5712 }
5713
5714 case Task::Reset:
5715 {
5716 /* The lock is also used as a signal from the task initiator (which
5717 * releases it only after RTThreadCreate()) that we can start the job */
5718 AutoWriteLock thatLock(that);
5719
5720 /// @todo Below we use a pair of delete/create operations to reset
5721 /// the diff contents but the most efficient way will of course be
5722 /// to add a VDResetDiff() API call
5723
5724 uint64_t size = 0, logicalSize = 0;
5725
5726 try
5727 {
5728 PVBOXHDD hdd;
5729 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5730 ComAssertRCThrow(vrc, E_FAIL);
5731
5732 Guid id = that->m->id;
5733 Utf8Str format(that->m->format);
5734 Utf8Str location(that->m->locationFull);
5735
5736 Guid parentId = that->mParent->m->id;
5737 Utf8Str parentFormat(that->mParent->m->format);
5738 Utf8Str parentLocation(that->mParent->m->locationFull);
5739
5740 Assert(that->m->state == MediumState_LockedWrite);
5741
5742 /* unlock before the potentially lengthy operation */
5743 thatLock.leave();
5744
5745 try
5746 {
5747 /* first, delete the storage unit */
5748 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5749 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5750 that->m->vdDiskIfaces);
5751 if (RT_SUCCESS(vrc))
5752 vrc = VDClose(hdd, true /* fDelete */);
5753
5754 if (RT_FAILURE(vrc))
5755 {
5756 throw setError(E_FAIL,
5757 tr("Could not delete the hard disk storage unit '%s'%s"),
5758 location.raw(), that->vdError(vrc).raw());
5759 }
5760
5761 /* next, create it again */
5762 vrc = VDOpen(hdd, parentFormat.c_str(), parentLocation.c_str(),
5763 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5764 that->m->vdDiskIfaces);
5765 if (RT_FAILURE(vrc))
5766 {
5767 throw setError(E_FAIL,
5768 tr("Could not open the hard disk storage unit '%s'%s"),
5769 parentLocation.raw(), that->vdError(vrc).raw());
5770 }
5771
5772 /* needed for vdProgressCallback */
5773 that->m->vdProgress = task->progress;
5774
5775 vrc = VDCreateDiff(hdd, format.c_str(), location.c_str(),
5776 /// @todo use the same image variant as before
5777 VD_IMAGE_FLAGS_NONE,
5778 NULL, id.raw(),
5779 parentId.raw(),
5780 VD_OPEN_FLAGS_NORMAL,
5781 that->m->vdDiskIfaces,
5782 that->m->vdDiskIfaces);
5783
5784 that->m->vdProgress = NULL;
5785
5786 if (RT_FAILURE(vrc))
5787 {
5788 throw setError(E_FAIL,
5789 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5790 location.raw(), that->vdError(vrc).raw());
5791 }
5792
5793 size = VDGetFileSize(hdd, 1);
5794 logicalSize = VDGetSize(hdd, 1) / _1M;
5795 }
5796 catch (HRESULT aRC) { rc = aRC; }
5797
5798 VDDestroy(hdd);
5799 }
5800 catch (HRESULT aRC) { rc = aRC; }
5801
5802 thatLock.enter();
5803
5804 that->m->size = size;
5805 that->m->logicalSize = logicalSize;
5806
5807 if (isAsync)
5808 {
5809 /* unlock ourselves when done */
5810 HRESULT rc2 = that->UnlockWrite(NULL);
5811 AssertComRC(rc2);
5812 }
5813
5814 /* Note that in sync mode, it's the caller's responsibility to
5815 * unlock the hard disk */
5816
5817 break;
5818 }
5819
5820 ////////////////////////////////////////////////////////////////////////
5821
5822 case Task::Compact:
5823 {
5824 /* Lock all in {parent,child} order. The lock is also used as a
5825 * signal from the task initiator (which releases it only after
5826 * RTThreadCreate()) that we can start the job. */
5827 AutoWriteLock thatLock(that);
5828
5829 ImageChain *imgChain = task->d.images.get();
5830
5831 try
5832 {
5833 PVBOXHDD hdd;
5834 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5835 ComAssertRCThrow(vrc, E_FAIL);
5836
5837 try
5838 {
5839 /* Open all hard disk images in the chain. */
5840 List::const_iterator last = imgChain->end();
5841 last--;
5842 for (List::const_iterator it = imgChain->begin();
5843 it != imgChain->end(); ++ it)
5844 {
5845 /* sanity check */
5846 if (it == last)
5847 Assert((*it)->m->state == MediumState_LockedWrite);
5848 else
5849 Assert((*it)->m->state == MediumState_LockedRead);
5850
5851 /** Open all images but last in read-only mode. */
5852 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5853 Utf8Str((*it)->m->locationFull).c_str(),
5854 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5855 (*it)->m->vdDiskIfaces);
5856 if (RT_FAILURE(vrc))
5857 {
5858 throw setError(E_FAIL,
5859 tr("Could not open the hard disk storage unit '%s'%s"),
5860 Utf8Str((*it)->m->locationFull).raw(),
5861 that->vdError(vrc).raw());
5862 }
5863 }
5864
5865 /* unlock before the potentially lengthy operation */
5866 thatLock.leave();
5867
5868 Assert(that->m->state == MediumState_LockedWrite);
5869
5870 /* needed for vdProgressCallback */
5871 that->m->vdProgress = task->progress;
5872
5873 vrc = VDCompact(hdd, VD_LAST_IMAGE, that->m->vdDiskIfaces);
5874
5875 that->m->vdProgress = NULL;
5876
5877 if (RT_FAILURE(vrc))
5878 {
5879 if (vrc == VERR_NOT_SUPPORTED)
5880 throw setError(VBOX_E_NOT_SUPPORTED,
5881 tr("Compacting is not supported yet for hard disk '%s'"),
5882 Utf8Str(that->m->locationFull).raw());
5883 else if (vrc == VERR_NOT_IMPLEMENTED)
5884 throw setError(E_NOTIMPL,
5885 tr("Compacting is not implemented, hard disk '%s'"),
5886 Utf8Str(that->m->locationFull).raw());
5887 else
5888 throw setError(E_FAIL,
5889 tr("Could not compact hard disk '%s'%s"),
5890 Utf8Str(that->m->locationFull).raw(),
5891 that->vdError(vrc).raw());
5892 }
5893 }
5894 catch (HRESULT aRC) { rc = aRC; }
5895
5896 VDDestroy(hdd);
5897 }
5898 catch (HRESULT aRC) { rc = aRC; }
5899
5900 /* Everything is explicitly unlocked when the task exits,
5901 * as the task destruction also destroys the image chain. */
5902
5903 break;
5904 }
5905
5906 default:
5907 AssertFailedReturn(VERR_GENERAL_FAILURE);
5908 }
5909
5910 /* complete the progress if run asynchronously */
5911 if (isAsync)
5912 {
5913 if (!task->progress.isNull())
5914 task->progress->notifyComplete(rc);
5915 }
5916 else
5917 {
5918 task->rc = rc;
5919 }
5920
5921 LogFlowFunc(("rc=%Rhrc\n", rc));
5922 LogFlowFuncLeave();
5923
5924 return VINF_SUCCESS;
5925
5926 /// @todo ugly hack, fix ComAssert... later
5927 #undef setError
5928}
5929
5930/* vi: set tabstop=4 shiftwidth=4 expandtab: */
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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