VirtualBox

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

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

Main: fix deadlock candidates detected by lock validator

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