VirtualBox

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

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

Main: Bstr makeover -- make Bstr(NULL) and Bstr() behave the same; resulting cleanup; make some more internal methods use Utf8Str instead of Bstr; fix a lot of CheckComArgNotNull() usage

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

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