VirtualBox

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

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

Main: remove templates for 'weak' com pointers which do nothing anyway

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

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