VirtualBox

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

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

back out r63543, r63544 until windows build problems can be solved properly

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

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