VirtualBox

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

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

Automated rebranding to Oracle copyright/license strings via filemuncher

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

© 2023 Oracle
ContactPrivacy policyTerms of Use