VirtualBox

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

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

Main: separate internal machine data structs into MachineImplPrivate.h to significantly speed up compilation and for better interface separation; remove obsolete ConsoleEvents.h file

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

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