VirtualBox

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

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

Main: do not hold any other lock while calling VirtualBox::saveSettings (mostly comments, only real change is in DHCPServer); also, VirtualBox lock is not needed in SessionMachine::endSavingState

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

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