VirtualBox

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

Last change on this file since 30935 was 30929, checked in by vboxsync, 14 years ago

Main: add option to UnregisterMachine() to automatically nuke all attached media

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

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