VirtualBox

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

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

Main/Medium: new stub medium type "Shareable", plus assorted frontend changes to prepare its use.

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

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