VirtualBox

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

Last change on this file since 30308 was 30292, checked in by vboxsync, 15 years ago

Main/Medium: add a couple of forgotten AutoCaller objects to fix extremely sporadic crashes

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