VirtualBox

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

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

VBoxHDD: introduce new function pointers to the TCP interface for getting the local and peer address of a socket

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