VirtualBox

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

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

Main/Medium: BOOL->bool in many places, gets rid of a gcc warning

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

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