VirtualBox

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

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

Main/Medium: meditum tree lock comment cleanup

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

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