VirtualBox

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

Last change on this file since 28098 was 27831, checked in by vboxsync, 15 years ago

Main/Medium: auto_ptr is evil, removed its use with the task objects

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

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