VirtualBox

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

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

Main: COM header cleanup (remove obscure and unused templates)

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

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