VirtualBox

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

Last change on this file since 30037 was 29940, checked in by vboxsync, 14 years ago

Main/Medium: fix a couple of lock order violations around calling VirtualBox::saveSettings, plus eliminating some leave() method calls where release() is obviously the same thing

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

© 2023 Oracle
ContactPrivacy policyTerms of Use