VirtualBox

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

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

Main: live snapshot merging. initially limited to forward merging (i.e. everything but the first snapshot can be deleted)

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

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