VirtualBox

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

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

Main/Medium: new helper method for fixing up parent UUIDs of children moved around by merging, make diff reset rely on documented VBoxHDD behavior by opening the entire medium chain, minor bugfixing to get the reported image sizes in the GUI right, fixing the parameters of an error message to print the right image name

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

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