VirtualBox

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

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

MediumImpl.cpp: Committed a temporary hack for the deadlock in Medium::COMSETTER(Type) while waiting for it to be fixed properly in the context of #4201.

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