VirtualBox

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

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

Main: document fFailIfInaccessible

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

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