VirtualBox

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

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

Main/MediumImpl,VirtualBoxBase: eliminate AutoMayUninitSpan, as it leads to deadlocks in Medium::Close (lock order violation between medium tree lock and the eventmulti semaphore used in AutoCaller).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 191.7 KB
Line 
1/* $Id: MediumImpl.cpp 26984 2010-03-03 13:07:00Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008-2010 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "MediumImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27#include "VirtualBoxImpl.h"
28
29#include "AutoCaller.h"
30#include "Logging.h"
31
32#include <VBox/com/array.h>
33#include <VBox/com/SupportErrorInfo.h>
34
35#include <VBox/err.h>
36#include <VBox/settings.h>
37
38#include <iprt/param.h>
39#include <iprt/path.h>
40#include <iprt/file.h>
41#include <iprt/tcp.h>
42
43#include <VBox/VBoxHDD.h>
44
45#include <algorithm>
46
47////////////////////////////////////////////////////////////////////////////////
48//
49// Medium data definition
50//
51////////////////////////////////////////////////////////////////////////////////
52
53/** Describes how a machine refers to this image. */
54struct BackRef
55{
56 /** Equality predicate for stdc++. */
57 struct EqualsTo : public std::unary_function <BackRef, bool>
58 {
59 explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
60
61 bool operator()(const argument_type &aThat) const
62 {
63 return aThat.machineId == machineId;
64 }
65
66 const Guid machineId;
67 };
68
69 typedef std::list<Guid> GuidList;
70
71 BackRef(const Guid &aMachineId,
72 const Guid &aSnapshotId = Guid::Empty)
73 : machineId(aMachineId),
74 fInCurState(aSnapshotId.isEmpty())
75 {
76 if (!aSnapshotId.isEmpty())
77 llSnapshotIds.push_back(aSnapshotId);
78 }
79
80 Guid machineId;
81 bool fInCurState : 1;
82 GuidList llSnapshotIds;
83};
84
85typedef std::list<BackRef> BackRefList;
86
87struct Medium::Data
88{
89 Data()
90 : state(MediumState_NotCreated),
91 size(0),
92 readers(0),
93 preLockState(MediumState_NotCreated),
94 queryInfoSem(NIL_RTSEMEVENTMULTI),
95 queryInfoRunning(false),
96 type(MediumType_Normal),
97 devType(DeviceType_HardDisk),
98 logicalSize(0),
99 hddOpenMode(OpenReadWrite),
100 autoReset(false),
101 setImageId(false),
102 setParentId(false),
103 hostDrive(FALSE),
104 implicit(false),
105 numCreateDiffTasks(0),
106 vdDiskIfaces(NULL)
107 {}
108
109 /** weak VirtualBox parent */
110 const ComObjPtr<VirtualBox, ComWeakRef> pVirtualBox;
111
112 const Guid id;
113 Utf8Str strDescription;
114 MediumState_T state;
115 Utf8Str strLocation;
116 Utf8Str strLocationFull;
117 uint64_t size;
118 Utf8Str strLastAccessError;
119
120 // pParent and llChildren are protected by VirtualBox::getMediaTreeLockHandle()
121 ComObjPtr<Medium> pParent;
122 MediaList llChildren; // to add a child, just call push_back; to remove a child, call child->deparent() which does a lookup
123
124 BackRefList backRefs;
125
126 size_t readers;
127 MediumState_T preLockState;
128
129 RTSEMEVENTMULTI queryInfoSem;
130 bool queryInfoRunning : 1;
131
132 const Utf8Str strFormat;
133 ComObjPtr<MediumFormat> formatObj;
134
135 MediumType_T type;
136 DeviceType_T devType;
137 uint64_t logicalSize; /*< In MBytes. */
138
139 HDDOpenMode hddOpenMode;
140
141 BOOL autoReset : 1;
142
143 /** the following members are invalid after changing UUID on open */
144 BOOL setImageId : 1;
145 BOOL setParentId : 1;
146 const Guid imageId;
147 const Guid parentId;
148
149 BOOL hostDrive : 1;
150
151 typedef std::map <Bstr, Bstr> PropertyMap;
152 PropertyMap properties;
153
154 bool implicit : 1;
155
156 uint32_t numCreateDiffTasks;
157
158 Utf8Str vdError; /*< Error remembered by the VD error callback. */
159
160 VDINTERFACE vdIfError;
161 VDINTERFACEERROR vdIfCallsError;
162
163 VDINTERFACE vdIfConfig;
164 VDINTERFACECONFIG vdIfCallsConfig;
165
166 VDINTERFACE vdIfTcpNet;
167 VDINTERFACETCPNET vdIfCallsTcpNet;
168
169 PVDINTERFACE vdDiskIfaces;
170};
171
172////////////////////////////////////////////////////////////////////////////////
173//
174// Globals
175//
176////////////////////////////////////////////////////////////////////////////////
177
178/**
179 * Asynchronous task thread parameter bucket.
180 *
181 * Note that instances of this class must be created using new() because the
182 * task thread function will delete them when the task is complete!
183 *
184 * @note The constructor of this class adds a caller on the managed Medium
185 * object which is automatically released upon destruction.
186 */
187struct Medium::Task : public com::SupportErrorInfoBase
188{
189 enum Operation { CreateBase,
190 CreateDiff,
191 Merge,
192 Clone,
193 Delete,
194 Reset,
195 Compact
196 };
197
198 Medium *that;
199 AutoCaller m_autoCaller;
200
201 ComObjPtr<Progress> m_pProgress;
202 Operation m_operation;
203
204 /** Where to save the result when executed using #runNow(). */
205 HRESULT m_rc;
206
207 // Whether the caller needs to call VirtualBox::saveSettings() after
208 // the task function returns. Only used in synchronous (wait) mode;
209 // otherwise the task will save the settings itself.
210 bool *m_pfNeedsSaveSettings;
211
212 Task(Medium *aThat,
213 Progress *aProgress,
214 Operation aOperation)
215 : that(aThat),
216 m_autoCaller(aThat),
217 m_pProgress(aProgress),
218 m_operation(aOperation),
219 m_rc(S_OK),
220 m_pfNeedsSaveSettings(NULL)
221 { }
222
223 ~Task();
224
225 void setData(Medium *aTarget)
226 {
227 d.target = aTarget;
228 HRESULT rc = d.target->addCaller();
229 AssertComRC(rc);
230 }
231
232 void setData(Medium *aTarget, Medium *aParent)
233 {
234 d.target = aTarget;
235 HRESULT rc = d.target->addCaller();
236 AssertComRC(rc);
237 d.parentDisk = aParent;
238 if (aParent)
239 {
240 rc = d.parentDisk->addCaller();
241 AssertComRC(rc);
242 }
243 }
244
245 void setData(MergeChain *aChain)
246 {
247 AssertReturnVoid(aChain != NULL);
248 d.chain.reset(aChain);
249 }
250
251 void setData(ImageChain *aSrcChain, ImageChain *aParentChain)
252 {
253 AssertReturnVoid(aSrcChain != NULL);
254 AssertReturnVoid(aParentChain != NULL);
255 d.source.reset(aSrcChain);
256 d.parent.reset(aParentChain);
257 }
258
259 void setData(ImageChain *aImgChain)
260 {
261 AssertReturnVoid(aImgChain != NULL);
262 d.images.reset(aImgChain);
263 }
264
265 HRESULT startThread();
266 HRESULT runNow(bool *pfNeedsSaveSettings);
267
268 struct Data
269 {
270 Data() : size(0) {}
271
272 /* CreateBase */
273
274 uint64_t size;
275
276 /* CreateBase, CreateDiff, Clone */
277
278 MediumVariant_T variant;
279
280 /* CreateDiff, Clone */
281
282 ComObjPtr<Medium> target;
283
284 /* Clone */
285
286 /** Media to open, in {parent,child} order */
287 std::auto_ptr<ImageChain> source;
288 /** Media which are parent of target, in {parent,child} order */
289 std::auto_ptr<ImageChain> parent;
290 /** The to-be parent medium object */
291 ComObjPtr<Medium> parentDisk;
292
293 /* Merge */
294
295 /** Media to merge, in {parent,child} order */
296 std::auto_ptr<MergeChain> chain;
297
298 /* Compact */
299
300 /** Media to open, in {parent,child} order */
301 std::auto_ptr<ImageChain> images;
302 } d;
303
304protected:
305
306 // SupportErrorInfoBase interface
307 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
308 const char *componentName() const { return Medium::ComponentName(); }
309};
310
311Medium::Task::~Task()
312{
313 /* remove callers added by setData() */
314 if (!d.target.isNull())
315 d.target->releaseCaller();
316}
317
318/**
319 * Starts a new thread driven by the Medium::taskThread() function and passes
320 * this Task instance as an argument.
321 *
322 * Note that if this method returns success, this Task object becomes an ownee
323 * of the started thread and will be automatically deleted when the thread
324 * terminates.
325 *
326 * @note When the task is executed by this method, IProgress::notifyComplete()
327 * is automatically called for the progress object associated with this
328 * task when the task is finished to signal the operation completion for
329 * other threads asynchronously waiting for it.
330 */
331HRESULT Medium::Task::startThread()
332{
333 int vrc = RTThreadCreate(NULL, Medium::taskThread, this,
334 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
335 "Medium::Task");
336 ComAssertMsgRCRet(vrc,
337 ("Could not create Medium::Task thread (%Rrc)\n", vrc),
338 E_FAIL);
339
340 return S_OK;
341}
342
343/**
344 * Runs Medium::taskThread() by passing it this Task instance as an argument
345 * on the current thread instead of creating a new one.
346 *
347 * This call implies that it is made on another temporary thread created for
348 * some asynchronous task. Avoid calling it from a normal thread since the task
349 * operatinos are potentially lengthy and will block the calling thread in this
350 * case.
351 *
352 * Note that this Task object will be deleted by taskThread() when this method
353 * returns!
354 *
355 * @note When the task is executed by this method, IProgress::notifyComplete()
356 * is not called for the progress object associated with this task when
357 * the task is finished. Instead, the result of the operation is returned
358 * by this method directly and it's the caller's responsibility to
359 * complete the progress object in this case.
360 */
361HRESULT Medium::Task::runNow(bool *pfNeedsSaveSettings)
362{
363 m_pfNeedsSaveSettings = pfNeedsSaveSettings;
364
365 /* NIL_RTTHREAD indicates synchronous call. */
366 Medium::taskThread(NIL_RTTHREAD, this);
367
368 return m_rc;
369}
370
371////////////////////////////////////////////////////////////////////////////////
372//
373// Merge chain class
374//
375////////////////////////////////////////////////////////////////////////////////
376
377/**
378 * Helper class for merge operations.
379 *
380 * @note It is assumed that when modifying methods of this class are called,
381 * Medium::getTreeLock() is held in read mode.
382 */
383class Medium::MergeChain : public MediaList,
384 public com::SupportErrorInfoBase
385{
386public:
387
388 MergeChain(bool aForward, bool aIgnoreAttachments)
389 : mForward(aForward)
390 , mIgnoreAttachments(aIgnoreAttachments) {}
391
392 ~MergeChain()
393 {
394 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
395 {
396 HRESULT rc = (*it)->UnlockWrite(NULL);
397 AssertComRC(rc);
398
399 (*it)->releaseCaller();
400 }
401
402 for (iterator it = begin(); it != end(); ++ it)
403 {
404 AutoWriteLock alock(*it COMMA_LOCKVAL_SRC_POS);
405 Assert((*it)->m->state == MediumState_LockedWrite ||
406 (*it)->m->state == MediumState_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 // we're accessing parent/child and backrefs, so lock the tree first, then ourselves
2026 AutoMultiWriteLock2 multilock(&m->pVirtualBox->getMediaTreeLockHandle(),
2027 this->lockHandle()
2028 COMMA_LOCKVAL_SRC_POS);
2029
2030 bool wasCreated = true;
2031 bool fNeedsSaveSettings = false;
2032
2033 switch (m->state)
2034 {
2035 case MediumState_NotCreated:
2036 wasCreated = false;
2037 break;
2038 case MediumState_Created:
2039 case MediumState_Inaccessible:
2040 break;
2041 default:
2042 return setStateError();
2043 }
2044
2045 if (m->backRefs.size() != 0)
2046 return setError(VBOX_E_OBJECT_IN_USE,
2047 tr("Medium '%s' is attached to %d virtual machines"),
2048 m->strLocationFull.raw(), m->backRefs.size());
2049
2050 /* perform extra media-dependent close checks */
2051 HRESULT rc = canClose();
2052 if (FAILED(rc)) return rc;
2053
2054 if (wasCreated)
2055 {
2056 /* remove from the list of known media before performing actual
2057 * uninitialization (to keep the media registry consistent on
2058 * failure to do so) */
2059 rc = unregisterWithVirtualBox(&fNeedsSaveSettings);
2060 if (FAILED(rc)) return rc;
2061 }
2062
2063 /* Keep the locks held until after uninit, as otherwise the consistency
2064 * of the medium tree cannot be guaranteed. */
2065 uninit();
2066
2067 multilock.release();
2068
2069 if (fNeedsSaveSettings)
2070 {
2071 AutoWriteLock vboxlock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
2072 m->pVirtualBox->saveSettings();
2073 }
2074
2075 return S_OK;
2076}
2077
2078STDMETHODIMP Medium::GetProperty(IN_BSTR aName, BSTR *aValue)
2079{
2080 CheckComArgStrNotEmptyOrNull(aName);
2081 CheckComArgOutPointerValid(aValue);
2082
2083 AutoCaller autoCaller(this);
2084 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2085
2086 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2087
2088 Data::PropertyMap::const_iterator it = m->properties.find(Bstr(aName));
2089 if (it == m->properties.end())
2090 return setError(VBOX_E_OBJECT_NOT_FOUND,
2091 tr("Property '%ls' does not exist"), aName);
2092
2093 it->second.cloneTo(aValue);
2094
2095 return S_OK;
2096}
2097
2098STDMETHODIMP Medium::SetProperty(IN_BSTR aName, IN_BSTR aValue)
2099{
2100 CheckComArgStrNotEmptyOrNull(aName);
2101
2102 AutoCaller autoCaller(this);
2103 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2104
2105 /* VirtualBox::saveSettings() needs a write lock */
2106 AutoMultiWriteLock2 alock(m->pVirtualBox, this COMMA_LOCKVAL_SRC_POS);
2107
2108 switch (m->state)
2109 {
2110 case MediumState_Created:
2111 case MediumState_Inaccessible:
2112 break;
2113 default:
2114 return setStateError();
2115 }
2116
2117 Data::PropertyMap::iterator it = m->properties.find(Bstr(aName));
2118 if (it == m->properties.end())
2119 return setError(VBOX_E_OBJECT_NOT_FOUND,
2120 tr("Property '%ls' does not exist"),
2121 aName);
2122
2123 if (aValue && !*aValue)
2124 it->second = (const char *)NULL;
2125 else
2126 it->second = aValue;
2127
2128 HRESULT rc = m->pVirtualBox->saveSettings();
2129
2130 return rc;
2131}
2132
2133STDMETHODIMP Medium::GetProperties(IN_BSTR aNames,
2134 ComSafeArrayOut(BSTR, aReturnNames),
2135 ComSafeArrayOut(BSTR, aReturnValues))
2136{
2137 CheckComArgOutSafeArrayPointerValid(aReturnNames);
2138 CheckComArgOutSafeArrayPointerValid(aReturnValues);
2139
2140 AutoCaller autoCaller(this);
2141 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2142
2143 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2144
2145 /// @todo make use of aNames according to the documentation
2146 NOREF(aNames);
2147
2148 com::SafeArray<BSTR> names(m->properties.size());
2149 com::SafeArray<BSTR> values(m->properties.size());
2150 size_t i = 0;
2151
2152 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2153 it != m->properties.end();
2154 ++it)
2155 {
2156 it->first.cloneTo(&names[i]);
2157 it->second.cloneTo(&values[i]);
2158 ++i;
2159 }
2160
2161 names.detachTo(ComSafeArrayOutArg(aReturnNames));
2162 values.detachTo(ComSafeArrayOutArg(aReturnValues));
2163
2164 return S_OK;
2165}
2166
2167STDMETHODIMP Medium::SetProperties(ComSafeArrayIn(IN_BSTR, aNames),
2168 ComSafeArrayIn(IN_BSTR, aValues))
2169{
2170 CheckComArgSafeArrayNotNull(aNames);
2171 CheckComArgSafeArrayNotNull(aValues);
2172
2173 AutoCaller autoCaller(this);
2174 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2175
2176 /* VirtualBox::saveSettings() needs a write lock */
2177 AutoMultiWriteLock2 alock(m->pVirtualBox, this COMMA_LOCKVAL_SRC_POS);
2178
2179 com::SafeArray<IN_BSTR> names(ComSafeArrayInArg(aNames));
2180 com::SafeArray<IN_BSTR> values(ComSafeArrayInArg(aValues));
2181
2182 /* first pass: validate names */
2183 for (size_t i = 0;
2184 i < names.size();
2185 ++i)
2186 {
2187 if (m->properties.find(Bstr(names[i])) == m->properties.end())
2188 return setError(VBOX_E_OBJECT_NOT_FOUND,
2189 tr("Property '%ls' does not exist"), names[i]);
2190 }
2191
2192 /* second pass: assign */
2193 for (size_t i = 0;
2194 i < names.size();
2195 ++i)
2196 {
2197 Data::PropertyMap::iterator it = m->properties.find(Bstr(names[i]));
2198 AssertReturn(it != m->properties.end(), E_FAIL);
2199
2200 if (values[i] && !*values[i])
2201 it->second = (const char *)NULL;
2202 else
2203 it->second = values[i];
2204 }
2205
2206 HRESULT rc = m->pVirtualBox->saveSettings();
2207
2208 return rc;
2209}
2210
2211STDMETHODIMP Medium::CreateBaseStorage(ULONG64 aLogicalSize,
2212 MediumVariant_T aVariant,
2213 IProgress **aProgress)
2214{
2215 CheckComArgOutPointerValid(aProgress);
2216
2217 AutoCaller autoCaller(this);
2218 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2219
2220 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2221
2222 aVariant = (MediumVariant_T)((unsigned)aVariant & (unsigned)~MediumVariant_Diff);
2223 if ( !(aVariant & MediumVariant_Fixed)
2224 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2225 return setError(VBOX_E_NOT_SUPPORTED,
2226 tr("Hard disk format '%s' does not support dynamic storage creation"),
2227 m->strFormat.raw());
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 fixed storage creation"),
2232 m->strFormat.raw());
2233
2234 switch (m->state)
2235 {
2236 case MediumState_NotCreated:
2237 break;
2238 default:
2239 return setStateError();
2240 }
2241
2242 ComObjPtr <Progress> progress;
2243 progress.createObject();
2244 /// @todo include fixed/dynamic
2245 HRESULT rc = progress->init(m->pVirtualBox,
2246 static_cast<IMedium*>(this),
2247 (aVariant & MediumVariant_Fixed)
2248 ? BstrFmt(tr("Creating fixed hard disk storage unit '%s'"), m->strLocationFull.raw())
2249 : BstrFmt(tr("Creating dynamic hard disk storage unit '%s'"), m->strLocationFull.raw()),
2250 TRUE /* aCancelable */);
2251 if (FAILED(rc)) return rc;
2252
2253 /* setup task object and thread to carry out the operation
2254 * asynchronously */
2255
2256 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateBase));
2257 AssertComRCReturnRC(task->m_autoCaller.rc());
2258
2259 task->d.size = aLogicalSize;
2260 task->d.variant = aVariant;
2261
2262 rc = task->startThread();
2263 if (FAILED(rc)) return rc;
2264
2265 /* go to Creating state on success */
2266 m->state = MediumState_Creating;
2267
2268 /* task is now owned by taskThread() so release it */
2269 task.release();
2270
2271 /* return progress to the caller */
2272 progress.queryInterfaceTo(aProgress);
2273
2274 return S_OK;
2275}
2276
2277STDMETHODIMP Medium::DeleteStorage(IProgress **aProgress)
2278{
2279 CheckComArgOutPointerValid(aProgress);
2280
2281 AutoCaller autoCaller(this);
2282 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2283
2284 ComObjPtr <Progress> progress;
2285
2286 HRESULT rc = deleteStorageNoWait(progress);
2287 if (SUCCEEDED(rc))
2288 {
2289 /* return progress to the caller */
2290 progress.queryInterfaceTo(aProgress);
2291 }
2292
2293 return rc;
2294}
2295
2296STDMETHODIMP Medium::CreateDiffStorage(IMedium *aTarget,
2297 MediumVariant_T aVariant,
2298 IProgress **aProgress)
2299{
2300 CheckComArgNotNull(aTarget);
2301 CheckComArgOutPointerValid(aProgress);
2302
2303 AutoCaller autoCaller(this);
2304 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2305
2306 ComObjPtr<Medium> diff = static_cast<Medium*>(aTarget);
2307
2308 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2309
2310 if (m->type == MediumType_Writethrough)
2311 return setError(E_FAIL,
2312 tr("Hard disk '%s' is Writethrough"),
2313 m->strLocationFull.raw());
2314
2315 /* We want to be locked for reading as long as our diff child is being
2316 * created */
2317 HRESULT rc = LockRead(NULL);
2318 if (FAILED(rc)) return rc;
2319
2320 ComObjPtr <Progress> progress;
2321
2322 rc = createDiffStorageNoWait(diff, aVariant, progress);
2323 if (FAILED(rc))
2324 {
2325 HRESULT rc2 = UnlockRead(NULL);
2326 AssertComRC(rc2);
2327 /* Note: on success, taskThread() will unlock this */
2328 }
2329 else
2330 {
2331 /* return progress to the caller */
2332 progress.queryInterfaceTo(aProgress);
2333 }
2334
2335 return rc;
2336}
2337
2338STDMETHODIMP Medium::MergeTo(IN_BSTR /* aTargetId */, IProgress ** /* aProgress */)
2339{
2340 AutoCaller autoCaller(this);
2341 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2342
2343 ReturnComNotImplemented();
2344}
2345
2346STDMETHODIMP Medium::CloneTo(IMedium *aTarget,
2347 MediumVariant_T aVariant,
2348 IMedium *aParent,
2349 IProgress **aProgress)
2350{
2351 CheckComArgNotNull(aTarget);
2352 CheckComArgOutPointerValid(aProgress);
2353
2354 AutoCaller autoCaller(this);
2355 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2356
2357 ComObjPtr<Medium> target = static_cast<Medium*>(aTarget);
2358 ComObjPtr<Medium> parent;
2359 if (aParent)
2360 parent = static_cast<Medium*>(aParent);
2361
2362 AutoMultiWriteLock3 alock(this, target, parent COMMA_LOCKVAL_SRC_POS);
2363
2364 ComObjPtr<Progress> progress;
2365 HRESULT rc = S_OK;
2366
2367 try
2368 {
2369 if ( target->m->state != MediumState_NotCreated
2370 && target->m->state != MediumState_Created)
2371 throw target->setStateError();
2372
2373 /** @todo separate out creating/locking an image chain from
2374 * SessionMachine::lockMedia and use it from here too.
2375 * logically this belongs into Medium functionality. */
2376
2377 /* Build the source chain and lock images in the proper order. */
2378 std::auto_ptr <ImageChain> srcChain(new ImageChain());
2379
2380 /* we walk the source tree */
2381 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2382 for (Medium *hd = this;
2383 hd;
2384 hd = hd->m->pParent)
2385 {
2386 rc = srcChain->addImage(hd);
2387 if (FAILED(rc)) throw rc;
2388 }
2389 rc = srcChain->lockImagesRead();
2390 if (FAILED(rc)) throw rc;
2391
2392 /* Build the parent chain and lock images in the proper order. */
2393 std::auto_ptr <ImageChain> parentChain(new ImageChain());
2394
2395 for (Medium *hd = parent;
2396 hd;
2397 hd = hd->m->pParent)
2398 {
2399 rc = parentChain->addImage(hd);
2400 if (FAILED(rc)) throw rc;
2401 }
2402 if (target->m->state == MediumState_Created)
2403 {
2404 /* If we're cloning to an existing image the parent chain also
2405 * contains the target image, and it gets locked for writing. */
2406 rc = parentChain->addImage(target);
2407 if (FAILED(rc)) throw rc;
2408 rc = parentChain->lockImagesReadAndLastWrite();
2409 if (FAILED(rc)) throw rc;
2410 }
2411 else
2412 {
2413 rc = parentChain->lockImagesRead();
2414 if (FAILED(rc)) throw rc;
2415 }
2416
2417 progress.createObject();
2418 rc = progress->init(m->pVirtualBox,
2419 static_cast <IMedium *>(this),
2420 BstrFmt(tr("Creating clone hard disk '%s'"), target->m->strLocationFull.raw()),
2421 TRUE /* aCancelable */);
2422 if (FAILED(rc)) throw rc;
2423
2424 /* setup task object and thread to carry out the operation
2425 * asynchronously */
2426
2427 std::auto_ptr<Task> task(new Task(this, progress, Task::Clone));
2428 AssertComRCThrowRC(task->m_autoCaller.rc());
2429
2430 task->setData(target, parent);
2431 task->d.variant = aVariant;
2432 task->setData(srcChain.release(), parentChain.release());
2433
2434 rc = task->startThread();
2435 if (FAILED(rc)) throw rc;
2436
2437 if (target->m->state == MediumState_NotCreated)
2438 {
2439 /* go to Creating state before leaving the lock */
2440 target->m->state = MediumState_Creating;
2441 }
2442
2443 /* task is now owned (or already deleted) by taskThread() so release it */
2444 task.release();
2445 }
2446 catch (HRESULT aRC)
2447 {
2448 rc = aRC;
2449 }
2450
2451 if (SUCCEEDED(rc))
2452 {
2453 /* return progress to the caller */
2454 progress.queryInterfaceTo(aProgress);
2455 }
2456
2457 return rc;
2458}
2459
2460STDMETHODIMP Medium::Compact(IProgress **aProgress)
2461{
2462 CheckComArgOutPointerValid(aProgress);
2463
2464 AutoCaller autoCaller(this);
2465 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2466
2467 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2468
2469 ComObjPtr <Progress> progress;
2470
2471 HRESULT rc = S_OK;
2472
2473 try
2474 {
2475 /** @todo separate out creating/locking an image chain from
2476 * SessionMachine::lockMedia and use it from here too.
2477 * logically this belongs into Medium functionality. */
2478
2479 /* Build the image chain and lock images in the proper order. */
2480 std::auto_ptr <ImageChain> imgChain(new ImageChain());
2481
2482 /* we walk the image tree */
2483 AutoReadLock srcTreeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2484 for (Medium *hd = this;
2485 hd;
2486 hd = hd->m->pParent)
2487 {
2488 rc = imgChain->addImage(hd);
2489 if (FAILED(rc)) throw rc;
2490 }
2491 rc = imgChain->lockImagesReadAndLastWrite();
2492 if (FAILED(rc)) throw rc;
2493
2494 progress.createObject();
2495 rc = progress->init(m->pVirtualBox,
2496 static_cast <IMedium *>(this),
2497 BstrFmt(tr("Compacting hard disk '%s'"), m->strLocationFull.raw()),
2498 TRUE /* aCancelable */);
2499 if (FAILED(rc)) throw rc;
2500
2501 /* setup task object and thread to carry out the operation
2502 * asynchronously */
2503
2504 std::auto_ptr <Task> task(new Task(this, progress, Task::Compact));
2505 AssertComRCThrowRC(task->m_autoCaller.rc());
2506
2507 task->setData(imgChain.release());
2508
2509 rc = task->startThread();
2510 if (FAILED(rc)) throw rc;
2511
2512 /* task is now owned (or already deleted) by taskThread() so release it */
2513 task.release();
2514 }
2515 catch (HRESULT aRC)
2516 {
2517 rc = aRC;
2518 }
2519
2520 if (SUCCEEDED(rc))
2521 {
2522 /* return progress to the caller */
2523 progress.queryInterfaceTo(aProgress);
2524 }
2525
2526 return rc;
2527}
2528
2529STDMETHODIMP Medium::Resize(ULONG64 aLogicalSize, IProgress **aProgress)
2530{
2531 CheckComArgOutPointerValid(aProgress);
2532
2533 AutoCaller autoCaller(this);
2534 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2535
2536 NOREF(aLogicalSize);
2537 NOREF(aProgress);
2538 ReturnComNotImplemented();
2539}
2540
2541STDMETHODIMP Medium::Reset(IProgress **aProgress)
2542{
2543 CheckComArgOutPointerValid(aProgress);
2544
2545 AutoCaller autoCaller(this);
2546 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2547
2548 /* canClose() needs the tree lock */
2549 AutoMultiWriteLock2 multilock(&m->pVirtualBox->getMediaTreeLockHandle(),
2550 this->lockHandle()
2551 COMMA_LOCKVAL_SRC_POS);
2552
2553 LogFlowThisFunc(("ENTER for medium %s\n", m->strLocationFull.c_str()));
2554
2555 if (m->pParent.isNull())
2556 return setError(VBOX_E_NOT_SUPPORTED,
2557 tr ("Hard disk '%s' is not differencing"),
2558 m->strLocationFull.raw());
2559
2560 HRESULT rc = canClose();
2561 if (FAILED(rc)) return rc;
2562
2563 rc = LockWrite(NULL);
2564 if (FAILED(rc)) return rc;
2565
2566 ComObjPtr <Progress> progress;
2567
2568 try
2569 {
2570 progress.createObject();
2571 rc = progress->init(m->pVirtualBox,
2572 static_cast<IMedium*>(this),
2573 BstrFmt(tr("Resetting differencing hard disk '%s'"), m->strLocationFull.raw()),
2574 FALSE /* aCancelable */);
2575 if (FAILED(rc)) throw rc;
2576
2577 /* setup task object and thread to carry out the operation
2578 * asynchronously */
2579 std::auto_ptr<Task> task(new Task(this, progress, Task::Reset));
2580 AssertComRCThrowRC(task->m_autoCaller.rc());
2581
2582 rc = task->startThread();
2583 if (FAILED(rc)) throw rc;
2584
2585 /* task is now owned (or already deleted) by taskThread() so release it */
2586 task.release();
2587 }
2588 catch (HRESULT aRC)
2589 {
2590 rc = aRC;
2591 }
2592
2593 if (FAILED(rc))
2594 {
2595 HRESULT rc2 = UnlockWrite(NULL);
2596 AssertComRC(rc2);
2597 /* Note: on success, taskThread() will unlock this */
2598 }
2599 else
2600 {
2601 /* return progress to the caller */
2602 progress.queryInterfaceTo(aProgress);
2603 }
2604
2605 LogFlowThisFunc(("LEAVE, rc=%Rhrc\n", rc));
2606
2607 return rc;
2608}
2609
2610////////////////////////////////////////////////////////////////////////////////
2611//
2612// Medium internal methods
2613//
2614////////////////////////////////////////////////////////////////////////////////
2615
2616/**
2617 * Internal method to return the medium's parent medium. Must have caller + locking!
2618 * @return
2619 */
2620const ComObjPtr<Medium>& Medium::getParent() const
2621{
2622 return m->pParent;
2623}
2624
2625/**
2626 * Internal method to return the medium's list of child media. Must have caller + locking!
2627 * @return
2628 */
2629const MediaList& Medium::getChildren() const
2630{
2631 return m->llChildren;
2632}
2633
2634/**
2635 * Internal method to return the medium's GUID. Must have caller + locking!
2636 * @return
2637 */
2638const Guid& Medium::getId() const
2639{
2640 return m->id;
2641}
2642
2643/**
2644 * Internal method to return the medium's GUID. Must have caller + locking!
2645 * @return
2646 */
2647MediumState_T Medium::getState() const
2648{
2649 return m->state;
2650}
2651
2652/**
2653 * Internal method to return the medium's location. Must have caller + locking!
2654 * @return
2655 */
2656const Utf8Str& Medium::getLocation() const
2657{
2658 return m->strLocation;
2659}
2660
2661/**
2662 * Internal method to return the medium's full location. Must have caller + locking!
2663 * @return
2664 */
2665const Utf8Str& Medium::getLocationFull() const
2666{
2667 return m->strLocationFull;
2668}
2669
2670/**
2671 * Internal method to return the medium's size. Must have caller + locking!
2672 * @return
2673 */
2674uint64_t Medium::getSize() const
2675{
2676 return m->size;
2677}
2678
2679/**
2680 * Adds the given machine and optionally the snapshot to the list of the objects
2681 * this image is attached to.
2682 *
2683 * @param aMachineId Machine ID.
2684 * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
2685 */
2686HRESULT Medium::attachTo(const Guid &aMachineId,
2687 const Guid &aSnapshotId /*= Guid::Empty*/)
2688{
2689 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2690
2691 LogFlowThisFunc(("ENTER, aMachineId: {%RTuuid}, aSnapshotId: {%RTuuid}\n", aMachineId.raw(), aSnapshotId.raw()));
2692
2693 AutoCaller autoCaller(this);
2694 AssertComRCReturnRC(autoCaller.rc());
2695
2696 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2697
2698 switch (m->state)
2699 {
2700 case MediumState_Created:
2701 case MediumState_Inaccessible:
2702 case MediumState_LockedRead:
2703 case MediumState_LockedWrite:
2704 break;
2705
2706 default:
2707 return setStateError();
2708 }
2709
2710 if (m->numCreateDiffTasks > 0)
2711 return setError(E_FAIL,
2712 tr("Cannot attach hard disk '%s' {%RTuuid}: %u differencing child hard disk(s) are being created"),
2713 m->strLocationFull.raw(),
2714 m->id.raw(),
2715 m->numCreateDiffTasks);
2716
2717 BackRefList::iterator it = std::find_if(m->backRefs.begin(),
2718 m->backRefs.end(),
2719 BackRef::EqualsTo(aMachineId));
2720 if (it == m->backRefs.end())
2721 {
2722 BackRef ref(aMachineId, aSnapshotId);
2723 m->backRefs.push_back(ref);
2724
2725 return S_OK;
2726 }
2727
2728 // if the caller has not supplied a snapshot ID, then we're attaching
2729 // to a machine a medium which represents the machine's current state,
2730 // so set the flag
2731 if (aSnapshotId.isEmpty())
2732 {
2733 /* sanity: no duplicate attachments */
2734 AssertReturn(!it->fInCurState, E_FAIL);
2735 it->fInCurState = true;
2736
2737 return S_OK;
2738 }
2739
2740 // otherwise: a snapshot medium is being attached
2741
2742 /* sanity: no duplicate attachments */
2743 for (BackRef::GuidList::const_iterator jt = it->llSnapshotIds.begin();
2744 jt != it->llSnapshotIds.end();
2745 ++jt)
2746 {
2747 const Guid &idOldSnapshot = *jt;
2748
2749 if (idOldSnapshot == aSnapshotId)
2750 {
2751#ifdef DEBUG
2752 dumpBackRefs();
2753#endif
2754 return setError(E_FAIL,
2755 tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by this snapshot!"),
2756 m->strLocationFull.raw(),
2757 m->id.raw(),
2758 aSnapshotId.raw(),
2759 idOldSnapshot.raw());
2760 }
2761 }
2762
2763 it->llSnapshotIds.push_back(aSnapshotId);
2764 it->fInCurState = false;
2765
2766 LogFlowThisFuncLeave();
2767
2768 return S_OK;
2769}
2770
2771/**
2772 * Removes the given machine and optionally the snapshot from the list of the
2773 * objects this image is attached to.
2774 *
2775 * @param aMachineId Machine ID.
2776 * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
2777 * attachment.
2778 */
2779HRESULT Medium::detachFrom(const Guid &aMachineId,
2780 const Guid &aSnapshotId /*= Guid::Empty*/)
2781{
2782 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2783
2784 AutoCaller autoCaller(this);
2785 AssertComRCReturnRC(autoCaller.rc());
2786
2787 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2788
2789 BackRefList::iterator it =
2790 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2791 BackRef::EqualsTo(aMachineId));
2792 AssertReturn(it != m->backRefs.end(), E_FAIL);
2793
2794 if (aSnapshotId.isEmpty())
2795 {
2796 /* remove the current state attachment */
2797 it->fInCurState = false;
2798 }
2799 else
2800 {
2801 /* remove the snapshot attachment */
2802 BackRef::GuidList::iterator jt =
2803 std::find(it->llSnapshotIds.begin(), it->llSnapshotIds.end(), aSnapshotId);
2804
2805 AssertReturn(jt != it->llSnapshotIds.end(), E_FAIL);
2806 it->llSnapshotIds.erase(jt);
2807 }
2808
2809 /* if the backref becomes empty, remove it */
2810 if (it->fInCurState == false && it->llSnapshotIds.size() == 0)
2811 m->backRefs.erase(it);
2812
2813 return S_OK;
2814}
2815
2816/**
2817 * Internal method to return the medium's list of backrefs. Must have caller + locking!
2818 * @return
2819 */
2820const Guid* Medium::getFirstMachineBackrefId() const
2821{
2822 if (!m->backRefs.size())
2823 return NULL;
2824
2825 return &m->backRefs.front().machineId;
2826}
2827
2828const Guid* Medium::getFirstMachineBackrefSnapshotId() const
2829{
2830 if (!m->backRefs.size())
2831 return NULL;
2832
2833 const BackRef &ref = m->backRefs.front();
2834 if (!ref.llSnapshotIds.size())
2835 return NULL;
2836
2837 return &ref.llSnapshotIds.front();
2838}
2839
2840#ifdef DEBUG
2841/**
2842 * Debugging helper that gets called after VirtualBox initialization that writes all
2843 * machine backreferences to the debug log.
2844 */
2845void Medium::dumpBackRefs()
2846{
2847 AutoCaller autoCaller(this);
2848 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2849
2850 LogFlowThisFunc(("Dumping backrefs for medium '%s':\n", m->strLocationFull.raw()));
2851
2852 for (BackRefList::iterator it2 = m->backRefs.begin();
2853 it2 != m->backRefs.end();
2854 ++it2)
2855 {
2856 const BackRef &ref = *it2;
2857 LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d)\n", ref.machineId.raw(), ref.fInCurState));
2858
2859 for (BackRef::GuidList::const_iterator jt2 = it2->llSnapshotIds.begin();
2860 jt2 != it2->llSnapshotIds.end();
2861 ++jt2)
2862 {
2863 const Guid &id = *jt2;
2864 LogFlowThisFunc((" Backref from snapshot {%RTuuid}\n", id.raw()));
2865 }
2866 }
2867}
2868#endif
2869
2870/**
2871 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2872 * of this media and updates it if necessary to reflect the new location.
2873 *
2874 * @param aOldPath Old path (full).
2875 * @param aNewPath New path (full).
2876 *
2877 * @note Locks this object for writing.
2878 */
2879HRESULT Medium::updatePath(const char *aOldPath, const char *aNewPath)
2880{
2881 AssertReturn(aOldPath, E_FAIL);
2882 AssertReturn(aNewPath, E_FAIL);
2883
2884 AutoCaller autoCaller(this);
2885 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2886
2887 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2888
2889 LogFlowThisFunc(("locationFull.before='%s'\n", m->strLocationFull.raw()));
2890
2891 Utf8Str path = m->strLocationFull;
2892
2893 if (RTPathStartsWith(path.c_str(), aOldPath))
2894 {
2895 Utf8Str newPath = Utf8StrFmt("%s%s", aNewPath,
2896 path.raw() + strlen(aOldPath));
2897 path = newPath;
2898
2899 m->pVirtualBox->calculateRelativePath(path, path);
2900
2901 unconst(m->strLocationFull) = newPath;
2902 unconst(m->strLocation) = path;
2903
2904 LogFlowThisFunc(("locationFull.after='%s'\n", m->strLocationFull.raw()));
2905 }
2906
2907 return S_OK;
2908}
2909
2910/**
2911 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2912 * of this hard disk or any its child and updates the paths if necessary to
2913 * reflect the new location.
2914 *
2915 * @param aOldPath Old path (full).
2916 * @param aNewPath New path (full).
2917 *
2918 * @note Locks getTreeLock() for reading, this object and all children for writing.
2919 */
2920void Medium::updatePaths(const char *aOldPath, const char *aNewPath)
2921{
2922 AssertReturnVoid(aOldPath);
2923 AssertReturnVoid(aNewPath);
2924
2925 AutoCaller autoCaller(this);
2926 AssertComRCReturnVoid(autoCaller.rc());
2927
2928 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2929
2930 /* we access children() */
2931 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2932
2933 updatePath(aOldPath, aNewPath);
2934
2935 /* update paths of all children */
2936 for (MediaList::const_iterator it = getChildren().begin();
2937 it != getChildren().end();
2938 ++ it)
2939 {
2940 (*it)->updatePaths(aOldPath, aNewPath);
2941 }
2942}
2943
2944/**
2945 * Returns the base hard disk of the hard disk chain this hard disk is part of.
2946 *
2947 * The base hard disk is found by walking up the parent-child relationship axis.
2948 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
2949 * returns itself in response to this method.
2950 *
2951 * @param aLevel Where to store the number of ancestors of this hard disk
2952 * (zero for the base), may be @c NULL.
2953 *
2954 * @note Locks getTreeLock() for reading.
2955 */
2956ComObjPtr<Medium> Medium::getBase(uint32_t *aLevel /*= NULL*/)
2957{
2958 ComObjPtr<Medium> pBase;
2959 uint32_t level;
2960
2961 AutoCaller autoCaller(this);
2962 AssertReturn(autoCaller.isOk(), pBase);
2963
2964 /* we access mParent */
2965 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
2966
2967 pBase = this;
2968 level = 0;
2969
2970 if (m->pParent)
2971 {
2972 for (;;)
2973 {
2974 AutoCaller baseCaller(pBase);
2975 AssertReturn(baseCaller.isOk(), pBase);
2976
2977 if (pBase->m->pParent.isNull())
2978 break;
2979
2980 pBase = pBase->m->pParent;
2981 ++level;
2982 }
2983 }
2984
2985 if (aLevel != NULL)
2986 *aLevel = level;
2987
2988 return pBase;
2989}
2990
2991/**
2992 * Returns @c true if this hard disk cannot be modified because it has
2993 * dependants (children) or is part of the snapshot. Related to the hard disk
2994 * type and posterity, not to the current media state.
2995 *
2996 * @note Locks this object and getTreeLock() for reading.
2997 */
2998bool Medium::isReadOnly()
2999{
3000 AutoCaller autoCaller(this);
3001 AssertComRCReturn(autoCaller.rc(), false);
3002
3003 /* we access children */
3004 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3005
3006 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3007
3008 switch (m->type)
3009 {
3010 case MediumType_Normal:
3011 {
3012 if (getChildren().size() != 0)
3013 return true;
3014
3015 for (BackRefList::const_iterator it = m->backRefs.begin();
3016 it != m->backRefs.end(); ++ it)
3017 if (it->llSnapshotIds.size() != 0)
3018 return true;
3019
3020 return false;
3021 }
3022 case MediumType_Immutable:
3023 {
3024 return true;
3025 }
3026 case MediumType_Writethrough:
3027 {
3028 return false;
3029 }
3030 default:
3031 break;
3032 }
3033
3034 AssertFailedReturn(false);
3035}
3036
3037/**
3038 * Saves hard disk data by appending a new <HardDisk> child node to the given
3039 * parent node which can be either <HardDisks> or <HardDisk>.
3040 *
3041 * @param data Settings struct to be updated.
3042 *
3043 * @note Locks this object, getTreeLock() and children for reading.
3044 */
3045HRESULT Medium::saveSettings(settings::Medium &data)
3046{
3047 AutoCaller autoCaller(this);
3048 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3049
3050 /* we access mParent */
3051 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3052
3053 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3054
3055 data.uuid = m->id;
3056 data.strLocation = m->strLocation;
3057 data.strFormat = m->strFormat;
3058
3059 /* optional, only for diffs, default is false */
3060 if (m->pParent)
3061 data.fAutoReset = !!m->autoReset;
3062 else
3063 data.fAutoReset = false;
3064
3065 /* optional */
3066 data.strDescription = m->strDescription;
3067
3068 /* optional properties */
3069 data.properties.clear();
3070 for (Data::PropertyMap::const_iterator it = m->properties.begin();
3071 it != m->properties.end();
3072 ++it)
3073 {
3074 /* only save properties that have non-default values */
3075 if (!it->second.isEmpty())
3076 {
3077 Utf8Str name = it->first;
3078 Utf8Str value = it->second;
3079 data.properties[name] = value;
3080 }
3081 }
3082
3083 /* only for base hard disks */
3084 if (m->pParent.isNull())
3085 data.hdType = m->type;
3086
3087 /* save all children */
3088 for (MediaList::const_iterator it = getChildren().begin();
3089 it != getChildren().end();
3090 ++it)
3091 {
3092 settings::Medium med;
3093 HRESULT rc = (*it)->saveSettings(med);
3094 AssertComRCReturnRC(rc);
3095 data.llChildren.push_back(med);
3096 }
3097
3098 return S_OK;
3099}
3100
3101/**
3102 * Compares the location of this hard disk to the given location.
3103 *
3104 * The comparison takes the location details into account. For example, if the
3105 * location is a file in the host's filesystem, a case insensitive comparison
3106 * will be performed for case insensitive filesystems.
3107 *
3108 * @param aLocation Location to compare to (as is).
3109 * @param aResult Where to store the result of comparison: 0 if locations
3110 * are equal, 1 if this object's location is greater than
3111 * the specified location, and -1 otherwise.
3112 */
3113HRESULT Medium::compareLocationTo(const char *aLocation, int &aResult)
3114{
3115 AutoCaller autoCaller(this);
3116 AssertComRCReturnRC(autoCaller.rc());
3117
3118 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3119
3120 Utf8Str locationFull(m->strLocationFull);
3121
3122 /// @todo NEWMEDIA delegate the comparison to the backend?
3123
3124 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3125 {
3126 Utf8Str location(aLocation);
3127
3128 /* For locations represented by files, append the default path if
3129 * only the name is given, and then get the full path. */
3130 if (!RTPathHavePath(aLocation))
3131 {
3132 location = Utf8StrFmt("%s%c%s",
3133 m->pVirtualBox->getDefaultHardDiskFolder().raw(),
3134 RTPATH_DELIMITER,
3135 aLocation);
3136 }
3137
3138 int vrc = m->pVirtualBox->calculateFullPath(location, location);
3139 if (RT_FAILURE(vrc))
3140 return setError(E_FAIL,
3141 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3142 location.raw(),
3143 vrc);
3144
3145 aResult = RTPathCompare(locationFull.c_str(), location.c_str());
3146 }
3147 else
3148 aResult = locationFull.compare(aLocation);
3149
3150 return S_OK;
3151}
3152
3153/**
3154 * Checks that this hard disk may be discarded and performs necessary state
3155 * changes. Must not be called for writethrough disks because there is nothing
3156 * to discard then.
3157 *
3158 * This method is to be called prior to calling the #discard() to perform
3159 * necessary consistency checks and place involved hard disks to appropriate
3160 * states. If #discard() is not called or fails, the state modifications
3161 * performed by this method must be undone by #cancelDiscard().
3162 *
3163 * See #discard() for more info about discarding hard disks.
3164 *
3165 * @param aChain Where to store the created merge chain (may return NULL
3166 * if no real merge is necessary).
3167 *
3168 * @note Caller must hold media tree lock for writing. This locks this object
3169 * and every medium object on the merge chain for writing.
3170 */
3171HRESULT Medium::prepareDiscard(MergeChain * &aChain)
3172{
3173 AutoCaller autoCaller(this);
3174 AssertComRCReturnRC(autoCaller.rc());
3175
3176 aChain = NULL;
3177
3178 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3179
3180 Assert(m->pVirtualBox->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
3181
3182 // Medium must not be writethrough at this point
3183 AssertReturn( m->type == MediumType_Normal
3184 || m->type == MediumType_Immutable, E_FAIL);
3185
3186 if (getChildren().size() == 0)
3187 {
3188 /* special treatment of the last hard disk in the chain: */
3189
3190 if (m->pParent.isNull())
3191 {
3192 /* lock only, to prevent any usage; discard() will unlock */
3193 return LockWrite(NULL);
3194 }
3195
3196 /* the differencing hard disk w/o children will be deleted, protect it
3197 * from attaching to other VMs (this is why Deleting) */
3198
3199 switch (m->state)
3200 {
3201 case MediumState_Created:
3202 m->state = MediumState_Deleting;
3203 break;
3204 default:
3205 return setStateError();
3206 }
3207
3208 /* aChain is intentionally NULL here */
3209
3210 return S_OK;
3211 }
3212
3213 /* not going multi-merge as it's too expensive */
3214 if (getChildren().size() > 1)
3215 return setError(E_FAIL,
3216 tr ("Hard disk '%s' has more than one child hard disk (%d)"),
3217 m->strLocationFull.raw(), getChildren().size());
3218
3219 /* this is a read-only hard disk with children; it must be associated with
3220 * exactly one snapshot (when the snapshot is being taken, none of the
3221 * current VM's hard disks may be attached to other VMs). Note that by the
3222 * time when discard() is called, there must be no any attachments at all
3223 * (the code calling prepareDiscard() should detach). */
3224 AssertReturn(m->backRefs.size() == 1, E_FAIL);
3225 AssertReturn(!m->backRefs.front().fInCurState, E_FAIL);
3226 AssertReturn(m->backRefs.front().llSnapshotIds.size() == 1, E_FAIL);
3227
3228 ComObjPtr<Medium> child = getChildren().front();
3229
3230 /* we keep this locked, so lock the affected child to make sure the lock
3231 * order is correct when calling prepareMergeTo() */
3232 AutoWriteLock childLock(child COMMA_LOCKVAL_SRC_POS);
3233
3234 /* delegate the rest to the profi */
3235 if (m->pParent.isNull())
3236 {
3237 /* base hard disk, backward merge */
3238 Assert(child->m->backRefs.size() == 1);
3239 if (child->m->backRefs.front().machineId != m->backRefs.front().machineId)
3240 {
3241 /* backward merge is too tricky, we'll just detach on discard, so
3242 * lock only, to prevent any usage; discard() will only unlock
3243 * (since we return NULL in aChain) */
3244 return LockWrite(NULL);
3245 }
3246
3247 return child->prepareMergeTo(this, aChain, true /* aIgnoreAttachments */);
3248 }
3249 else
3250 {
3251 /* forward merge */
3252 return prepareMergeTo(child, aChain, true /* aIgnoreAttachments */);
3253 }
3254}
3255
3256/**
3257 * Discards this hard disk.
3258 *
3259 * Discarding the hard disk is merging its contents to its differencing child
3260 * hard disk (forward merge) or contents of its child hard disk to itself
3261 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
3262 * a differencing hard disk w/o children, then it will be simply deleted.
3263 * Calling this method on a base hard disk w/o children will do nothing and
3264 * silently succeed. If this hard disk has more than one child, the method will
3265 * currently return an error (since merging in this case would be too expensive
3266 * and result in data duplication).
3267 *
3268 * When the backward merge takes place (i.e. this hard disk is a target) then,
3269 * on success, this hard disk will automatically replace the differencing child
3270 * hard disk used as a source (which will then be deleted) in the attachment
3271 * this child hard disk is associated with. This will happen only if both hard
3272 * disks belong to the same machine because otherwise such a replace would be
3273 * too tricky and could be not expected by the other machine. Same relates to a
3274 * case when the child hard disk is not associated with any machine at all. When
3275 * the backward merge is not applied, the method behaves as if the base hard
3276 * disk were not attached at all -- i.e. simply detaches it from the machine but
3277 * leaves the hard disk chain intact.
3278 *
3279 * This method is basically a wrapper around #mergeTo() that selects the correct
3280 * merge direction and performs additional actions as described above and.
3281 *
3282 * Note that this method will not return until the merge operation is complete
3283 * (which may be quite time consuming depending on the size of the merged hard
3284 * disks).
3285 *
3286 * Note that #prepareDiscard() must be called before calling this method. If
3287 * this method returns a failure, the caller must call #cancelDiscard(). On
3288 * success, #cancelDiscard() must not be called (this method will perform all
3289 * necessary steps such as resetting states of all involved hard disks and
3290 * deleting @a aChain).
3291 *
3292 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3293 * no real merge takes place).
3294 *
3295 * @note Locks the hard disks from the chain for writing. Locks the machine
3296 * object when the backward merge takes place. Locks getTreeLock() lock for
3297 * reading or writing.
3298 */
3299HRESULT Medium::discard(ComObjPtr<Progress> &aProgress,
3300 ULONG ulWeight,
3301 MergeChain *aChain,
3302 bool *pfNeedsSaveSettings)
3303{
3304 AssertReturn(!aProgress.isNull(), E_FAIL);
3305
3306 ComObjPtr <Medium> hdFrom;
3307
3308 HRESULT rc = S_OK;
3309
3310 {
3311 AutoCaller autoCaller(this);
3312 AssertComRCReturnRC(autoCaller.rc());
3313
3314 aProgress->SetNextOperation(BstrFmt(tr("Merging differencing image '%s'"), getName().raw()),
3315 ulWeight); // weight
3316
3317 if (aChain == NULL)
3318 {
3319 /* we access mParent & children() */
3320 AutoMultiWriteLock2 mLock(&m->pVirtualBox->getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
3321
3322 Assert(getChildren().size() == 0);
3323
3324 /* special treatment of the last hard disk in the chain: */
3325
3326 if (m->pParent.isNull())
3327 {
3328 rc = UnlockWrite(NULL);
3329 AssertComRC(rc);
3330 return rc;
3331 }
3332
3333 /* delete the differencing hard disk w/o children */
3334
3335 Assert(m->state == MediumState_Deleting);
3336
3337 /* go back to Created since deleteStorage() expects this state */
3338 m->state = MediumState_Created;
3339
3340 hdFrom = this;
3341
3342 rc = deleteStorageAndWait(&aProgress, pfNeedsSaveSettings);
3343 }
3344 else
3345 {
3346 hdFrom = aChain->source();
3347
3348 rc = hdFrom->mergeToAndWait(aChain, &aProgress, pfNeedsSaveSettings);
3349 }
3350 }
3351
3352 if (SUCCEEDED(rc))
3353 {
3354 /* mergeToAndWait() cannot uninitialize the initiator because of
3355 * possible AutoCallers on the current thread, deleteStorageAndWait()
3356 * doesn't do it either; do it ourselves */
3357 hdFrom->uninit();
3358 }
3359
3360 return rc;
3361}
3362
3363/**
3364 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
3365 * or fails. Frees memory occupied by @a aChain.
3366 *
3367 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3368 * no real merge takes place).
3369 *
3370 * @note Locks the hard disks from the chain for writing. Locks getTreeLock() for
3371 * reading.
3372 */
3373void Medium::cancelDiscard(MergeChain *aChain)
3374{
3375 AutoCaller autoCaller(this);
3376 AssertComRCReturnVoid(autoCaller.rc());
3377
3378 if (aChain == NULL)
3379 {
3380 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3381
3382 /* we access mParent & children() */
3383 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3384
3385 Assert(getChildren().size() == 0);
3386
3387 /* special treatment of the last hard disk in the chain: */
3388
3389 if (m->pParent.isNull())
3390 {
3391 HRESULT rc = UnlockWrite(NULL);
3392 AssertComRC(rc);
3393 return;
3394 }
3395
3396 /* the differencing hard disk w/o children will be deleted, protect it
3397 * from attaching to other VMs (this is why Deleting) */
3398
3399 Assert(m->state == MediumState_Deleting);
3400 m->state = MediumState_Created;
3401
3402 return;
3403 }
3404
3405 /* delegate the rest to the profi */
3406 cancelMergeTo(aChain);
3407}
3408
3409/**
3410 * Returns a preferred format for differencing hard disks.
3411 */
3412Bstr Medium::preferredDiffFormat()
3413{
3414 Utf8Str strFormat;
3415
3416 AutoCaller autoCaller(this);
3417 AssertComRCReturn(autoCaller.rc(), strFormat);
3418
3419 /* m->format is const, no need to lock */
3420 strFormat = m->strFormat;
3421
3422 /* check that our own format supports diffs */
3423 if (!(m->formatObj->capabilities() & MediumFormatCapabilities_Differencing))
3424 {
3425 /* use the default format if not */
3426 AutoReadLock propsLock(m->pVirtualBox->systemProperties() COMMA_LOCKVAL_SRC_POS);
3427 strFormat = m->pVirtualBox->getDefaultHardDiskFormat();
3428 }
3429
3430 return strFormat;
3431}
3432
3433/**
3434 * Returns the medium type. Must have caller + locking!
3435 * @return
3436 */
3437MediumType_T Medium::getType() const
3438{
3439 return m->type;
3440}
3441
3442// private methods
3443////////////////////////////////////////////////////////////////////////////////
3444
3445/**
3446 * Returns a short version of the location attribute.
3447 *
3448 * @note Must be called from under this object's read or write lock.
3449 */
3450Utf8Str Medium::getName()
3451{
3452 Utf8Str name = RTPathFilename(m->strLocationFull.c_str());
3453 return name;
3454}
3455
3456/**
3457 * Sets the value of m->strLocation and calculates the value of m->strLocationFull.
3458 *
3459 * Treats non-FS-path locations specially, and prepends the default hard disk
3460 * folder if the given location string does not contain any path information
3461 * at all.
3462 *
3463 * Also, if the specified location is a file path that ends with '/' then the
3464 * file name part will be generated by this method automatically in the format
3465 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
3466 * and assign to this medium, and <ext> is the default extension for this
3467 * medium's storage format. Note that this procedure requires the media state to
3468 * be NotCreated and will return a failure otherwise.
3469 *
3470 * @param aLocation Location of the storage unit. If the location is a FS-path,
3471 * then it can be relative to the VirtualBox home directory.
3472 * @param aFormat Optional fallback format if it is an import and the format
3473 * cannot be determined.
3474 *
3475 * @note Must be called from under this object's write lock.
3476 */
3477HRESULT Medium::setLocation(const Utf8Str &aLocation, const Utf8Str &aFormat)
3478{
3479 AssertReturn(!aLocation.isEmpty(), E_FAIL);
3480
3481 AutoCaller autoCaller(this);
3482 AssertComRCReturnRC(autoCaller.rc());
3483
3484 /* formatObj may be null only when initializing from an existing path and
3485 * no format is known yet */
3486 AssertReturn( (!m->strFormat.isEmpty() && !m->formatObj.isNull())
3487 || ( autoCaller.state() == InInit
3488 && m->state != MediumState_NotCreated
3489 && m->id.isEmpty()
3490 && m->strFormat.isEmpty()
3491 && m->formatObj.isNull()),
3492 E_FAIL);
3493
3494 /* are we dealing with a new medium constructed using the existing
3495 * location? */
3496 bool isImport = m->strFormat.isEmpty();
3497
3498 if ( isImport
3499 || ( (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3500 && !m->hostDrive))
3501 {
3502 Guid id;
3503
3504 Utf8Str location(aLocation);
3505
3506 if (m->state == MediumState_NotCreated)
3507 {
3508 /* must be a file (formatObj must be already known) */
3509 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3510
3511 if (RTPathFilename(location.c_str()) == NULL)
3512 {
3513 /* no file name is given (either an empty string or ends with a
3514 * slash), generate a new UUID + file name if the state allows
3515 * this */
3516
3517 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3518 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3519 E_FAIL);
3520
3521 Bstr ext = m->formatObj->fileExtensions().front();
3522 ComAssertMsgRet(!ext.isEmpty(),
3523 ("Default extension must not be empty\n"),
3524 E_FAIL);
3525
3526 id.create();
3527
3528 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3529 location.raw(), id.raw(), ext.raw());
3530 }
3531 }
3532
3533 /* append the default folder if no path is given */
3534 if (!RTPathHavePath(location.c_str()))
3535 location = Utf8StrFmt("%s%c%s",
3536 m->pVirtualBox->getDefaultHardDiskFolder().raw(),
3537 RTPATH_DELIMITER,
3538 location.raw());
3539
3540 /* get the full file name */
3541 Utf8Str locationFull;
3542 int vrc = m->pVirtualBox->calculateFullPath(location, locationFull);
3543 if (RT_FAILURE(vrc))
3544 return setError(VBOX_E_FILE_ERROR,
3545 tr("Invalid medium storage file location '%s' (%Rrc)"),
3546 location.raw(), vrc);
3547
3548 /* detect the backend from the storage unit if importing */
3549 if (isImport)
3550 {
3551 char *backendName = NULL;
3552
3553 /* is it a file? */
3554 {
3555 RTFILE file;
3556 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
3557 if (RT_SUCCESS(vrc))
3558 RTFileClose(file);
3559 }
3560 if (RT_SUCCESS(vrc))
3561 {
3562 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3563 }
3564 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3565 {
3566 /* assume it's not a file, restore the original location */
3567 location = locationFull = aLocation;
3568 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3569 }
3570
3571 if (RT_FAILURE(vrc))
3572 {
3573 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3574 return setError(VBOX_E_FILE_ERROR,
3575 tr("Could not find file for the medium '%s' (%Rrc)"),
3576 locationFull.raw(), vrc);
3577 else if (aFormat.isEmpty())
3578 return setError(VBOX_E_IPRT_ERROR,
3579 tr("Could not get the storage format of the medium '%s' (%Rrc)"),
3580 locationFull.raw(), vrc);
3581 else
3582 {
3583 HRESULT rc = setFormat(Bstr(aFormat));
3584 /* setFormat() must not fail since we've just used the backend so
3585 * the format object must be there */
3586 AssertComRCReturnRC(rc);
3587 }
3588 }
3589 else
3590 {
3591 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3592
3593 HRESULT rc = setFormat(Bstr(backendName));
3594 RTStrFree(backendName);
3595
3596 /* setFormat() must not fail since we've just used the backend so
3597 * the format object must be there */
3598 AssertComRCReturnRC(rc);
3599 }
3600 }
3601
3602 /* is it still a file? */
3603 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3604 {
3605 m->strLocation = location;
3606 m->strLocationFull = locationFull;
3607
3608 if (m->state == MediumState_NotCreated)
3609 {
3610 /* assign a new UUID (this UUID will be used when calling
3611 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3612 * also do that if we didn't generate it to make sure it is
3613 * either generated by us or reset to null */
3614 unconst(m->id) = id;
3615 }
3616 }
3617 else
3618 {
3619 m->strLocation = locationFull;
3620 m->strLocationFull = locationFull;
3621 }
3622 }
3623 else
3624 {
3625 m->strLocation = aLocation;
3626 m->strLocationFull = aLocation;
3627 }
3628
3629 return S_OK;
3630}
3631
3632/**
3633 * Queries information from the image file.
3634 *
3635 * As a result of this call, the accessibility state and data members such as
3636 * size and description will be updated with the current information.
3637 *
3638 * @note This method may block during a system I/O call that checks storage
3639 * accessibility.
3640 *
3641 * @note Locks getTreeLock() for reading and writing (for new diff media checked
3642 * for the first time). Locks mParent for reading. Locks this object for
3643 * writing.
3644 */
3645HRESULT Medium::queryInfo()
3646{
3647 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3648
3649 if ( m->state != MediumState_Created
3650 && m->state != MediumState_Inaccessible
3651 && m->state != MediumState_LockedRead)
3652 return E_FAIL;
3653
3654 HRESULT rc = S_OK;
3655
3656 int vrc = VINF_SUCCESS;
3657
3658 /* check if a blocking queryInfo() call is in progress on some other thread,
3659 * and wait for it to finish if so instead of querying data ourselves */
3660 if (m->queryInfoRunning)
3661 {
3662 Assert( m->state == MediumState_LockedRead
3663 || m->state == MediumState_LockedWrite);
3664
3665 alock.leave();
3666
3667 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3668
3669 alock.enter();
3670
3671 AssertRC(vrc);
3672
3673 return S_OK;
3674 }
3675
3676 bool success = false;
3677 Utf8Str lastAccessError;
3678
3679 /* are we dealing with a new medium constructed using the existing
3680 * location? */
3681 bool isImport = m->id.isEmpty();
3682 unsigned flags = VD_OPEN_FLAGS_INFO;
3683
3684 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3685 * media because that would prevent necessary modifications
3686 * when opening media of some third-party formats for the first
3687 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3688 * generate an UUID if it is missing) */
3689 if ( (m->hddOpenMode == OpenReadOnly)
3690 || !isImport
3691 )
3692 flags |= VD_OPEN_FLAGS_READONLY;
3693
3694 /* Lock the medium, which makes the behavior much more consistent */
3695 if (flags & VD_OPEN_FLAGS_READONLY)
3696 rc = LockRead(NULL);
3697 else
3698 rc = LockWrite(NULL);
3699 if (FAILED(rc)) return rc;
3700
3701 /* Copies of the input state fields which are not read-only,
3702 * as we're dropping the lock. CAUTION: be extremely careful what
3703 * you do with the contents of this medium object, as you will
3704 * create races if there are concurrent changes. */
3705 Utf8Str format(m->strFormat);
3706 Utf8Str location(m->strLocationFull);
3707 ComObjPtr<MediumFormat> formatObj = m->formatObj;
3708
3709 /* "Output" values which can't be set because the lock isn't held
3710 * at the time the values are determined. */
3711 Guid mediumId = m->id;
3712 uint64_t mediumSize = 0;
3713 uint64_t mediumLogicalSize = 0;
3714
3715 /* leave the lock before a lengthy operation */
3716 vrc = RTSemEventMultiReset(m->queryInfoSem);
3717 ComAssertRCThrow(vrc, E_FAIL);
3718 m->queryInfoRunning = true;
3719 alock.leave();
3720
3721 try
3722 {
3723 /* skip accessibility checks for host drives */
3724 if (m->hostDrive)
3725 {
3726 success = true;
3727 throw S_OK;
3728 }
3729
3730 PVBOXHDD hdd;
3731 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3732 ComAssertRCThrow(vrc, E_FAIL);
3733
3734 try
3735 {
3736 /** @todo This kind of opening of images is assuming that diff
3737 * images can be opened as base images. Should be fixed ASAP. */
3738 vrc = VDOpen(hdd,
3739 format.c_str(),
3740 location.c_str(),
3741 flags,
3742 m->vdDiskIfaces);
3743 if (RT_FAILURE(vrc))
3744 {
3745 lastAccessError = Utf8StrFmt(tr("Could not open the medium '%s'%s"),
3746 location.c_str(), vdError(vrc).c_str());
3747 throw S_OK;
3748 }
3749
3750 if (formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3751 {
3752 /* Modify the UUIDs if necessary. The associated fields are
3753 * not modified by other code, so no need to copy. */
3754 if (m->setImageId)
3755 {
3756 vrc = VDSetUuid(hdd, 0, m->imageId);
3757 ComAssertRCThrow(vrc, E_FAIL);
3758 }
3759 if (m->setParentId)
3760 {
3761 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3762 ComAssertRCThrow(vrc, E_FAIL);
3763 }
3764 /* zap the information, these are no long-term members */
3765 m->setImageId = false;
3766 unconst(m->imageId).clear();
3767 m->setParentId = false;
3768 unconst(m->parentId).clear();
3769
3770 /* check the UUID */
3771 RTUUID uuid;
3772 vrc = VDGetUuid(hdd, 0, &uuid);
3773 ComAssertRCThrow(vrc, E_FAIL);
3774
3775 if (isImport)
3776 {
3777 mediumId = uuid;
3778
3779 if (mediumId.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3780 // only when importing a VDMK that has no UUID, create one in memory
3781 mediumId.create();
3782 }
3783 else
3784 {
3785 Assert(!mediumId.isEmpty());
3786
3787 if (mediumId != uuid)
3788 {
3789 lastAccessError = Utf8StrFmt(
3790 tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"),
3791 &uuid,
3792 location.c_str(),
3793 mediumId.raw(),
3794 m->pVirtualBox->settingsFilePath().c_str());
3795 throw S_OK;
3796 }
3797 }
3798 }
3799 else
3800 {
3801 /* the backend does not support storing UUIDs within the
3802 * underlying storage so use what we store in XML */
3803
3804 /* generate an UUID for an imported UUID-less medium */
3805 if (isImport)
3806 {
3807 if (m->setImageId)
3808 mediumId = m->imageId;
3809 else
3810 mediumId.create();
3811 }
3812 }
3813
3814 /* check the type */
3815 unsigned uImageFlags;
3816 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3817 ComAssertRCThrow(vrc, E_FAIL);
3818
3819 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3820 {
3821 RTUUID parentId;
3822 vrc = VDGetParentUuid(hdd, 0, &parentId);
3823 ComAssertRCThrow(vrc, E_FAIL);
3824
3825 if (isImport)
3826 {
3827 /* the parent must be known to us. Note that we freely
3828 * call locking methods of mVirtualBox and parent from the
3829 * write lock (breaking the {parent,child} lock order)
3830 * because there may be no concurrent access to the just
3831 * opened hard disk on ther threads yet (and init() will
3832 * fail if this method reporst MediumState_Inaccessible) */
3833
3834 Guid id = parentId;
3835 ComObjPtr<Medium> parent;
3836 rc = m->pVirtualBox->findHardDisk(&id, NULL,
3837 false /* aSetError */,
3838 &parent);
3839 if (FAILED(rc))
3840 {
3841 lastAccessError = Utf8StrFmt(
3842 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%s' is not found in the media registry ('%s')"),
3843 &parentId, location.c_str(),
3844 m->pVirtualBox->settingsFilePath().c_str());
3845 throw S_OK;
3846 }
3847
3848 /* we set mParent & children() */
3849 AutoWriteLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3850
3851 Assert(m->pParent.isNull());
3852 m->pParent = parent;
3853 m->pParent->m->llChildren.push_back(this);
3854 }
3855 else
3856 {
3857 /* we access mParent */
3858 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3859
3860 /* check that parent UUIDs match. Note that there's no need
3861 * for the parent's AutoCaller (our lifetime is bound to
3862 * it) */
3863
3864 if (m->pParent.isNull())
3865 {
3866 lastAccessError = Utf8StrFmt(
3867 tr("Hard disk '%s' is differencing but it is not associated with any parent hard disk in the media registry ('%s')"),
3868 location.c_str(),
3869 m->pVirtualBox->settingsFilePath().c_str());
3870 throw S_OK;
3871 }
3872
3873 AutoReadLock parentLock(m->pParent COMMA_LOCKVAL_SRC_POS);
3874 if ( m->pParent->getState() != MediumState_Inaccessible
3875 && m->pParent->getId() != parentId)
3876 {
3877 lastAccessError = Utf8StrFmt(
3878 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')"),
3879 &parentId, location.c_str(),
3880 m->pParent->getId().raw(),
3881 m->pVirtualBox->settingsFilePath().c_str());
3882 throw S_OK;
3883 }
3884
3885 /// @todo NEWMEDIA what to do if the parent is not
3886 /// accessible while the diff is? Probably, nothing. The
3887 /// real code will detect the mismatch anyway.
3888 }
3889 }
3890
3891 mediumSize = VDGetFileSize(hdd, 0);
3892 mediumLogicalSize = VDGetSize(hdd, 0) / _1M;
3893
3894 success = true;
3895 }
3896 catch (HRESULT aRC)
3897 {
3898 rc = aRC;
3899 }
3900
3901 VDDestroy(hdd);
3902
3903 }
3904 catch (HRESULT aRC)
3905 {
3906 rc = aRC;
3907 }
3908
3909 alock.enter();
3910
3911 if (isImport)
3912 unconst(m->id) = mediumId;
3913
3914 if (success)
3915 {
3916 m->size = mediumSize;
3917 m->logicalSize = mediumLogicalSize;
3918 m->strLastAccessError.setNull();
3919 }
3920 else
3921 {
3922 m->strLastAccessError = lastAccessError;
3923 LogWarningFunc(("'%s' is not accessible (error='%s', rc=%Rhrc, vrc=%Rrc)\n",
3924 location.c_str(), m->strLastAccessError.c_str(),
3925 rc, vrc));
3926 }
3927
3928 /* inform other callers if there are any */
3929 RTSemEventMultiSignal(m->queryInfoSem);
3930 m->queryInfoRunning = false;
3931
3932 /* Set the proper state according to the result of the check */
3933 if (success)
3934 m->preLockState = MediumState_Created;
3935 else
3936 m->preLockState = MediumState_Inaccessible;
3937
3938 if (flags & VD_OPEN_FLAGS_READONLY)
3939 rc = UnlockRead(NULL);
3940 else
3941 rc = UnlockWrite(NULL);
3942 if (FAILED(rc)) return rc;
3943
3944 return rc;
3945}
3946
3947/**
3948 * Sets the extended error info according to the current media state.
3949 *
3950 * @note Must be called from under this object's write or read lock.
3951 */
3952HRESULT Medium::setStateError()
3953{
3954 HRESULT rc = E_FAIL;
3955
3956 switch (m->state)
3957 {
3958 case MediumState_NotCreated:
3959 {
3960 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3961 tr("Storage for the medium '%s' is not created"),
3962 m->strLocationFull.raw());
3963 break;
3964 }
3965 case MediumState_Created:
3966 {
3967 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3968 tr("Storage for the medium '%s' is already created"),
3969 m->strLocationFull.raw());
3970 break;
3971 }
3972 case MediumState_LockedRead:
3973 {
3974 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3975 tr("Medium '%s' is locked for reading by another task"),
3976 m->strLocationFull.raw());
3977 break;
3978 }
3979 case MediumState_LockedWrite:
3980 {
3981 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3982 tr("Medium '%s' is locked for writing by another task"),
3983 m->strLocationFull.raw());
3984 break;
3985 }
3986 case MediumState_Inaccessible:
3987 {
3988 /* be in sync with Console::powerUpThread() */
3989 if (!m->strLastAccessError.isEmpty())
3990 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3991 tr("Medium '%s' is not accessible. %s"),
3992 m->strLocationFull.raw(), m->strLastAccessError.c_str());
3993 else
3994 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3995 tr("Medium '%s' is not accessible"),
3996 m->strLocationFull.raw());
3997 break;
3998 }
3999 case MediumState_Creating:
4000 {
4001 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
4002 tr("Storage for the medium '%s' is being created"),
4003 m->strLocationFull.raw());
4004 break;
4005 }
4006 case MediumState_Deleting:
4007 {
4008 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
4009 tr("Storage for the medium '%s' is being deleted"),
4010 m->strLocationFull.raw());
4011 break;
4012 }
4013 default:
4014 {
4015 AssertFailed();
4016 break;
4017 }
4018 }
4019
4020 return rc;
4021}
4022
4023/**
4024 * Deletes the hard disk storage unit.
4025 *
4026 * If @a aProgress is not NULL but the object it points to is @c null then a new
4027 * progress object will be created and assigned to @a *aProgress on success,
4028 * otherwise the existing progress object is used. If Progress is NULL, then no
4029 * progress object is created/used at all.
4030 *
4031 * When @a aWait is @c false, this method will create a thread to perform the
4032 * delete operation asynchronously and will return immediately. Otherwise, it
4033 * will perform the operation on the calling thread and will not return to the
4034 * caller until the operation is completed. Note that @a aProgress cannot be
4035 * NULL when @a aWait is @c false (this method will assert in this case).
4036 *
4037 * @param aProgress Where to find/store a Progress object to track operation
4038 * completion.
4039 * @param aWait @c true if this method should block instead of creating
4040 * an asynchronous thread.
4041 * @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
4042 * by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
4043 * This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
4044 * and this parameter is ignored.
4045 *
4046 * @note Locks mVirtualBox and this object for writing. Locks getTreeLock() for
4047 * writing.
4048 */
4049HRESULT Medium::deleteStorage(ComObjPtr<Progress> *aProgress,
4050 bool aWait,
4051 bool *pfNeedsSaveSettings)
4052{
4053 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4054
4055 /* we're accessing the media tree, and canClose() needs the tree lock too */
4056 AutoMultiWriteLock2 multilock(&m->pVirtualBox->getMediaTreeLockHandle(),
4057 this->lockHandle()
4058 COMMA_LOCKVAL_SRC_POS);
4059 LogFlowThisFunc(("aWait=%RTbool locationFull=%s\n", aWait, getLocationFull().c_str() ));
4060
4061 if ( !(m->formatObj->capabilities() & ( MediumFormatCapabilities_CreateDynamic
4062 | MediumFormatCapabilities_CreateFixed)))
4063 return setError(VBOX_E_NOT_SUPPORTED,
4064 tr("Hard disk format '%s' does not support storage deletion"),
4065 m->strFormat.raw());
4066
4067 /* Note that we are fine with Inaccessible state too: a) for symmetry with
4068 * create calls and b) because it doesn't really harm to try, if it is
4069 * really inaccessible, the delete operation will fail anyway. Accepting
4070 * Inaccessible state is especially important because all registered hard
4071 * disks are initially Inaccessible upon VBoxSVC startup until
4072 * COMGETTER(State) is called. */
4073
4074 switch (m->state)
4075 {
4076 case MediumState_Created:
4077 case MediumState_Inaccessible:
4078 break;
4079 default:
4080 return setStateError();
4081 }
4082
4083 if (m->backRefs.size() != 0)
4084 {
4085 Utf8Str strMachines;
4086 for (BackRefList::const_iterator it = m->backRefs.begin();
4087 it != m->backRefs.end();
4088 ++it)
4089 {
4090 const BackRef &b = *it;
4091 if (strMachines.length())
4092 strMachines.append(", ");
4093 strMachines.append(b.machineId.toString().c_str());
4094 }
4095#ifdef DEBUG
4096 dumpBackRefs();
4097#endif
4098 return setError(VBOX_E_OBJECT_IN_USE,
4099 tr("Cannot delete storage: hard disk '%s' is still attached to the following %d virtual machine(s): %s"),
4100 m->strLocationFull.c_str(),
4101 m->backRefs.size(),
4102 strMachines.c_str());
4103 }
4104
4105 HRESULT rc = canClose();
4106 if (FAILED(rc)) return rc;
4107
4108 /* go to Deleting state before leaving the lock */
4109 m->state = MediumState_Deleting;
4110
4111 /* try to remove from the list of known hard disks before performing actual
4112 * deletion (we favor the consistency of the media registry in the first
4113 * place which would have been broken if unregisterWithVirtualBox() failed
4114 * after we successfully deleted the storage) */
4115 rc = unregisterWithVirtualBox(pfNeedsSaveSettings);
4116
4117 /* restore the state because we may fail below; we will set it later again*/
4118 m->state = MediumState_Created;
4119
4120 if (FAILED(rc)) return rc;
4121
4122 ComObjPtr<Progress> progress;
4123
4124 if (aProgress != NULL)
4125 {
4126 /* use the existing progress object... */
4127 progress = *aProgress;
4128
4129 /* ...but create a new one if it is null */
4130 if (progress.isNull())
4131 {
4132 progress.createObject();
4133 rc = progress->init(m->pVirtualBox,
4134 static_cast<IMedium*>(this),
4135 BstrFmt(tr("Deleting hard disk storage unit '%s'"), m->strLocationFull.raw()),
4136 FALSE /* aCancelable */);
4137 if (FAILED(rc)) return rc;
4138 }
4139 }
4140
4141 std::auto_ptr<Task> task(new Task(this, progress, Task::Delete));
4142 AssertComRCReturnRC(task->m_autoCaller.rc());
4143
4144 if (aWait)
4145 {
4146 /* go to Deleting state before starting the task */
4147 m->state = MediumState_Deleting;
4148
4149 rc = task->runNow(NULL /* pfNeedsSaveSettings*/ ); // there is no save settings to do in taskThreadDelete()
4150 }
4151 else
4152 {
4153 rc = task->startThread();
4154 if (FAILED(rc)) return rc;
4155
4156 /* go to Deleting state before leaving the lock */
4157 m->state = MediumState_Deleting;
4158 }
4159
4160 /* task is now owned (or already deleted) by taskThread() so release it */
4161 task.release();
4162
4163 if (aProgress != NULL)
4164 {
4165 /* return progress to the caller */
4166 *aProgress = progress;
4167 }
4168
4169 return rc;
4170}
4171
4172/**
4173 * Creates a new differencing storage unit using the given target hard disk's
4174 * format and the location. Note that @c aTarget must be NotCreated.
4175 *
4176 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4177 * this hard disk for reading assuming that the caller has already done so. This
4178 * is used when taking an online snaopshot (where all original hard disks are
4179 * locked for writing and must remain such). Note however that if @a aWait is
4180 * @c false and this method returns a success then the thread started by
4181 * this method will unlock the hard disk (unless it is in
4182 * MediumState_LockedWrite state) so make sure the hard disk is either in
4183 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4184 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4185 * make sure you do it yourself as needed.
4186 *
4187 * If @a aProgress is not NULL but the object it points to is @c null then a new
4188 * progress object will be created and assigned to @a *aProgress on success,
4189 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4190 * progress object is created/used at all.
4191 *
4192 * When @a aWait is @c false, this method will create a thread to perform the
4193 * create operation asynchronously and will return immediately. Otherwise, it
4194 * will perform the operation on the calling thread and will not return to the
4195 * caller until the operation is completed. Note that @a aProgress cannot be
4196 * NULL when @a aWait is @c false (this method will assert in this case).
4197 *
4198 * @param aTarget Target hard disk.
4199 * @param aVariant Precise image variant to create.
4200 * @param aProgress Where to find/store a Progress object to track operation
4201 * completion.
4202 * @param aWait @c true if this method should block instead of creating
4203 * an asynchronous thread.
4204 * @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
4205 * by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
4206 * This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
4207 * and this parameter is ignored.
4208 *
4209 * @note Locks this object and @a aTarget for writing.
4210 */
4211HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4212 MediumVariant_T aVariant,
4213 ComObjPtr<Progress> *aProgress,
4214 bool aWait,
4215 bool *pfNeedsSaveSettings)
4216{
4217 AssertReturn(!aTarget.isNull(), E_FAIL);
4218 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4219
4220 AutoCaller autoCaller(this);
4221 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4222
4223 AutoCaller targetCaller(aTarget);
4224 if (FAILED(targetCaller.rc())) return targetCaller.rc();
4225
4226 AutoMultiWriteLock2 alock(this, aTarget COMMA_LOCKVAL_SRC_POS);
4227
4228 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4229
4230 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4231 AssertReturn( m->state == MediumState_LockedRead
4232 || m->state == MediumState_LockedWrite, E_FAIL);
4233
4234 if (aTarget->m->state != MediumState_NotCreated)
4235 return aTarget->setStateError();
4236
4237 HRESULT rc = S_OK;
4238
4239 /* check that the hard disk is not attached to any VM in the current state*/
4240 for (BackRefList::const_iterator it = m->backRefs.begin();
4241 it != m->backRefs.end();
4242 ++it)
4243 {
4244 if (it->fInCurState)
4245 {
4246 /* Note: when a VM snapshot is being taken, all normal hard disks
4247 * attached to the VM in the current state will be, as an exception,
4248 * also associated with the snapshot which is about to create (see
4249 * SnapshotMachine::init()) before deassociating them from the
4250 * current state (which takes place only on success in
4251 * Machine::fixupHardDisks()), so that the size of snapshotIds
4252 * will be 1 in this case. The given condition is used to filter out
4253 * this legal situatinon and do not report an error. */
4254
4255 if (it->llSnapshotIds.size() == 0)
4256 return setError(VBOX_E_INVALID_OBJECT_STATE,
4257 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"),
4258 m->strLocationFull.raw(), it->machineId.raw());
4259
4260 Assert(it->llSnapshotIds.size() == 1);
4261 }
4262 }
4263
4264 ComObjPtr<Progress> progress;
4265
4266 if (aProgress != NULL)
4267 {
4268 /* use the existing progress object... */
4269 progress = *aProgress;
4270
4271 /* ...but create a new one if it is null */
4272 if (progress.isNull())
4273 {
4274 progress.createObject();
4275 rc = progress->init(m->pVirtualBox,
4276 static_cast<IMedium*>(this),
4277 BstrFmt(tr("Creating differencing hard disk storage unit '%s'"), aTarget->m->strLocationFull.raw()),
4278 TRUE /* aCancelable */);
4279 if (FAILED(rc)) return rc;
4280 }
4281 }
4282
4283 /* set up task object and thread to carry out the operation
4284 * asynchronously */
4285
4286 std::auto_ptr<Task> task(new Task(this, progress, Task::CreateDiff));
4287 AssertComRCReturnRC(task->m_autoCaller.rc());
4288
4289 task->setData(aTarget);
4290 task->d.variant = aVariant;
4291
4292 /* register a task (it will deregister itself when done) */
4293 ++m->numCreateDiffTasks;
4294 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4295
4296 if (aWait)
4297 {
4298 // go to Creating state before starting the task
4299 aTarget->m->state = MediumState_Creating;
4300
4301 // release the locks because the task function will acquire other locks;
4302 // this is safe because both this and the target are not protected by
4303 // their states; we have asserted above that *this* is locked read or write!
4304 alock.release();
4305
4306 rc = task->runNow(pfNeedsSaveSettings);
4307 }
4308 else
4309 {
4310 rc = task->startThread();
4311 if (FAILED(rc)) return rc;
4312
4313 /* go to Creating state before leaving the lock */
4314 aTarget->m->state = MediumState_Creating;
4315 }
4316
4317 /* task is now owned (or already deleted) by taskThread() so release it */
4318 task.release();
4319
4320 if (aProgress != NULL)
4321 {
4322 /* return progress to the caller */
4323 *aProgress = progress;
4324 }
4325
4326 return rc;
4327}
4328
4329/**
4330 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4331 * disks for the merge operation.
4332 *
4333 * This method is to be called prior to calling the #mergeTo() to perform
4334 * necessary consistency checks and place involved hard disks to appropriate
4335 * states. If #mergeTo() is not called or fails, the state modifications
4336 * performed by this method must be undone by #cancelMergeTo().
4337 *
4338 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4339 * responsibility to detach the source and all intermediate hard disks before
4340 * calling #mergeTo() (which will fail otherwise).
4341 *
4342 * See #mergeTo() for more information about merging.
4343 *
4344 * @param aTarget Target hard disk.
4345 * @param aChain Where to store the created merge chain.
4346 * @param aIgnoreAttachments Don't check if the source or any intermediate
4347 * hard disk is attached to any VM.
4348 *
4349 * @note Locks getTreeLock() for reading. Locks this object, aTarget and all
4350 * intermediate hard disks for writing.
4351 */
4352HRESULT Medium::prepareMergeTo(Medium *aTarget,
4353 MergeChain * &aChain,
4354 bool aIgnoreAttachments /*= false*/)
4355{
4356 AssertReturn(aTarget != NULL, E_FAIL);
4357
4358 AutoCaller autoCaller(this);
4359 AssertComRCReturnRC(autoCaller.rc());
4360
4361 AutoCaller targetCaller(aTarget);
4362 AssertComRCReturnRC(targetCaller.rc());
4363
4364 aChain = NULL;
4365
4366 /* we walk the tree */
4367 AutoReadLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
4368
4369 HRESULT rc = S_OK;
4370
4371 /* detect the merge direction */
4372 bool forward;
4373 {
4374 Medium *parent = m->pParent;
4375 while (parent != NULL && parent != aTarget)
4376 parent = parent->m->pParent;
4377 if (parent == aTarget)
4378 forward = false;
4379 else
4380 {
4381 parent = aTarget->m->pParent;
4382 while (parent != NULL && parent != this)
4383 parent = parent->m->pParent;
4384 if (parent == this)
4385 forward = true;
4386 else
4387 {
4388 Utf8Str tgtLoc;
4389 {
4390 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4391 tgtLoc = aTarget->getLocationFull();
4392 }
4393
4394 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4395 return setError(E_FAIL,
4396 tr("Hard disks '%s' and '%s' are unrelated"),
4397 m->strLocationFull.raw(), tgtLoc.raw());
4398 }
4399 }
4400 }
4401
4402 /* build the chain (will do necessary checks and state changes) */
4403 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4404 aIgnoreAttachments));
4405 {
4406 Medium *last = forward ? aTarget : this;
4407 Medium *first = forward ? this : aTarget;
4408
4409 for (;;)
4410 {
4411 if (last == aTarget)
4412 rc = chain->addTarget(last);
4413 else if (last == this)
4414 rc = chain->addSource(last);
4415 else
4416 rc = chain->addIntermediate(last);
4417 if (FAILED(rc)) return rc;
4418
4419 if (last == first)
4420 break;
4421
4422 last = last->m->pParent;
4423 }
4424 }
4425
4426 aChain = chain.release();
4427
4428 return S_OK;
4429}
4430
4431/**
4432 * Merges this hard disk to the specified hard disk which must be either its
4433 * direct ancestor or descendant.
4434 *
4435 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4436 * get two varians of the merge operation:
4437 *
4438 * forward merge
4439 * ------------------------->
4440 * [Extra] <- SOURCE <- Intermediate <- TARGET
4441 * Any Del Del LockWr
4442 *
4443 *
4444 * backward merge
4445 * <-------------------------
4446 * TARGET <- Intermediate <- SOURCE <- [Extra]
4447 * LockWr Del Del LockWr
4448 *
4449 * Each scheme shows the involved hard disks on the hard disk chain where
4450 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4451 * the hard disk must have at a time of the mergeTo() call.
4452 *
4453 * The hard disks in the square braces may be absent (e.g. when the forward
4454 * operation takes place and SOURCE is the base hard disk, or when the backward
4455 * merge operation takes place and TARGET is the last child in the chain) but if
4456 * they present they are involved too as shown.
4457 *
4458 * Nor the source hard disk neither intermediate hard disks may be attached to
4459 * any VM directly or in the snapshot, otherwise this method will assert.
4460 *
4461 * The #prepareMergeTo() method must be called prior to this method to place all
4462 * involved to necessary states and perform other consistency checks.
4463 *
4464 * If @a aWait is @c true then this method will perform the operation on the
4465 * calling thread and will not return to the caller until the operation is
4466 * completed. When this method succeeds, all intermediate hard disk objects in
4467 * the chain will be uninitialized, the state of the target hard disk (and all
4468 * involved extra hard disks) will be restored and @a aChain will be deleted.
4469 * Note that this (source) hard disk is not uninitialized because of possible
4470 * AutoCaller instances held by the caller of this method on the current thread.
4471 * It's therefore the responsibility of the caller to call Medium::uninit()
4472 * after releasing all callers in this case!
4473 *
4474 * If @a aWait is @c false then this method will crea,te a thread to perform the
4475 * create operation asynchronously and will return immediately. If the operation
4476 * succeeds, the thread will uninitialize the source hard disk object and all
4477 * intermediate hard disk objects in the chain, reset the state of the target
4478 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4479 * operation fails, the thread will only reset the states of all involved hard
4480 * disks and delete @a aChain.
4481 *
4482 * When this method fails (regardless of the @a aWait mode), it is a caller's
4483 * responsiblity to undo state changes and delete @a aChain using
4484 * #cancelMergeTo().
4485 *
4486 * If @a aProgress is not NULL but the object it points to is @c null then a new
4487 * progress object will be created and assigned to @a *aProgress on success,
4488 * otherwise the existing progress object is used. If Progress is NULL, then no
4489 * progress object is created/used at all. Note that @a aProgress cannot be
4490 * NULL when @a aWait is @c false (this method will assert in this case).
4491 *
4492 * @param aChain Merge chain created by #prepareMergeTo().
4493 * @param aProgress Where to find/store a Progress object to track operation
4494 * completion.
4495 * @param aWait @c true if this method should block instead of creating
4496 * an asynchronous thread.
4497 * @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
4498 * by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
4499 * This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
4500 * and this parameter is ignored.
4501 *
4502 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4503 * for writing.
4504 */
4505HRESULT Medium::mergeTo(MergeChain *aChain,
4506 ComObjPtr <Progress> *aProgress,
4507 bool aWait,
4508 bool *pfNeedsSaveSettings)
4509{
4510 AssertReturn(aChain != NULL, E_FAIL);
4511 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4512
4513 AutoCaller autoCaller(this);
4514 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4515
4516 HRESULT rc = S_OK;
4517
4518 ComObjPtr <Progress> progress;
4519
4520 if (aProgress != NULL)
4521 {
4522 /* use the existing progress object... */
4523 progress = *aProgress;
4524
4525 /* ...but create a new one if it is null */
4526 if (progress.isNull())
4527 {
4528 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4529
4530 progress.createObject();
4531 rc = progress->init(m->pVirtualBox,
4532 static_cast<IMedium*>(this),
4533 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4534 getName().raw(),
4535 aChain->target()->getName().raw()),
4536 TRUE /* aCancelable */);
4537 if (FAILED(rc)) return rc;
4538 }
4539 }
4540
4541 /* setup task object and thread to carry out the operation
4542 * asynchronously */
4543
4544 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4545 AssertComRCReturnRC(task->m_autoCaller.rc());
4546
4547 task->setData(aChain);
4548
4549 /* Note: task owns aChain (will delete it when not needed) in all cases
4550 * except when @a aWait is @c true and runNow() fails -- in this case
4551 * aChain will be left away because cancelMergeTo() will be applied by the
4552 * caller on it as it is required in the documentation above */
4553
4554 if (aWait)
4555 {
4556 rc = task->runNow(pfNeedsSaveSettings);
4557 }
4558 else
4559 {
4560 rc = task->startThread();
4561 if (FAILED(rc)) return rc;
4562 }
4563
4564 /* task is now owned (or already deleted) by taskThread() so release it */
4565 task.release();
4566
4567 if (aProgress != NULL)
4568 {
4569 /* return progress to the caller */
4570 *aProgress = progress;
4571 }
4572
4573 return rc;
4574}
4575
4576/**
4577 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4578 * or fails. Frees memory occupied by @a aChain.
4579 *
4580 * @param aChain Merge chain created by #prepareMergeTo().
4581 *
4582 * @note Locks the hard disks from the chain for writing.
4583 */
4584void Medium::cancelMergeTo(MergeChain *aChain)
4585{
4586 AutoCaller autoCaller(this);
4587 AssertComRCReturnVoid(autoCaller.rc());
4588
4589 AssertReturnVoid(aChain != NULL);
4590
4591 /* the destructor will do the thing */
4592 delete aChain;
4593}
4594
4595/**
4596 * Checks that the format ID is valid and sets it on success.
4597 *
4598 * Note that this method will caller-reference the format object on success!
4599 * This reference must be released somewhere to let the MediumFormat object be
4600 * uninitialized.
4601 *
4602 * @note Must be called from under this object's write lock.
4603 */
4604HRESULT Medium::setFormat(CBSTR aFormat)
4605{
4606 /* get the format object first */
4607 {
4608 AutoReadLock propsLock(m->pVirtualBox->systemProperties() COMMA_LOCKVAL_SRC_POS);
4609
4610 unconst(m->formatObj)
4611 = m->pVirtualBox->systemProperties()->mediumFormat(aFormat);
4612 if (m->formatObj.isNull())
4613 return setError(E_INVALIDARG,
4614 tr("Invalid hard disk storage format '%ls'"), aFormat);
4615
4616 /* reference the format permanently to prevent its unexpected
4617 * uninitialization */
4618 HRESULT rc = m->formatObj->addCaller();
4619 AssertComRCReturnRC(rc);
4620
4621 /* get properties (preinsert them as keys in the map). Note that the
4622 * map doesn't grow over the object life time since the set of
4623 * properties is meant to be constant. */
4624
4625 Assert(m->properties.empty());
4626
4627 for (MediumFormat::PropertyList::const_iterator it =
4628 m->formatObj->properties().begin();
4629 it != m->formatObj->properties().end();
4630 ++ it)
4631 {
4632 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4633 }
4634 }
4635
4636 unconst(m->strFormat) = aFormat;
4637
4638 return S_OK;
4639}
4640
4641/**
4642 * @note Also reused by Medium::Reset().
4643 *
4644 * @note Caller must hold the media tree write lock!
4645 */
4646HRESULT Medium::canClose()
4647{
4648 Assert(m->pVirtualBox->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
4649
4650 if (getChildren().size() != 0)
4651 return setError(E_FAIL,
4652 tr("Cannot close medium '%s' because it has %d child hard disk(s)"),
4653 m->strLocationFull.raw(), getChildren().size());
4654
4655 return S_OK;
4656}
4657
4658/**
4659 * Calls either VirtualBox::unregisterImage or VirtualBox::unregisterHardDisk depending
4660 * on the device type of this medium.
4661 *
4662 * @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
4663 * by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
4664 *
4665 * @note Caller must have locked the media tree lock for writing!
4666 */
4667HRESULT Medium::unregisterWithVirtualBox(bool *pfNeedsSaveSettings)
4668{
4669 /* Note that we need to de-associate ourselves from the parent to let
4670 * unregisterHardDisk() properly save the registry */
4671
4672 /* we modify mParent and access children */
4673 Assert(m->pVirtualBox->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
4674
4675 Medium *pParentBackup = m->pParent;
4676 AssertReturn(getChildren().size() == 0, E_FAIL);
4677 if (m->pParent)
4678 deparent();
4679
4680 HRESULT rc = E_FAIL;
4681 switch (m->devType)
4682 {
4683 case DeviceType_DVD:
4684 rc = m->pVirtualBox->unregisterImage(this, DeviceType_DVD, pfNeedsSaveSettings);
4685 break;
4686
4687 case DeviceType_Floppy:
4688 rc = m->pVirtualBox->unregisterImage(this, DeviceType_Floppy, pfNeedsSaveSettings);
4689 break;
4690
4691 case DeviceType_HardDisk:
4692 rc = m->pVirtualBox->unregisterHardDisk(this, pfNeedsSaveSettings);
4693 break;
4694
4695 default:
4696 break;
4697 }
4698
4699 if (FAILED(rc))
4700 {
4701 if (pParentBackup)
4702 {
4703 /* re-associate with the parent as we are still relatives in the
4704 * registry */
4705 m->pParent = pParentBackup;
4706 m->pParent->m->llChildren.push_back(this);
4707 }
4708 }
4709
4710 return rc;
4711}
4712
4713/**
4714 * Returns the last error message collected by the vdErrorCall callback and
4715 * resets it.
4716 *
4717 * The error message is returned prepended with a dot and a space, like this:
4718 * <code>
4719 * ". <error_text> (%Rrc)"
4720 * </code>
4721 * to make it easily appendable to a more general error message. The @c %Rrc
4722 * format string is given @a aVRC as an argument.
4723 *
4724 * If there is no last error message collected by vdErrorCall or if it is a
4725 * null or empty string, then this function returns the following text:
4726 * <code>
4727 * " (%Rrc)"
4728 * </code>
4729 *
4730 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4731 * the callback isn't called by more than one thread at a time.
4732 *
4733 * @param aVRC VBox error code to use when no error message is provided.
4734 */
4735Utf8Str Medium::vdError(int aVRC)
4736{
4737 Utf8Str error;
4738
4739 if (m->vdError.isEmpty())
4740 error = Utf8StrFmt(" (%Rrc)", aVRC);
4741 else
4742 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4743
4744 m->vdError.setNull();
4745
4746 return error;
4747}
4748
4749/**
4750 * Error message callback.
4751 *
4752 * Puts the reported error message to the m->vdError field.
4753 *
4754 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4755 * the callback isn't called by more than one thread at a time.
4756 *
4757 * @param pvUser The opaque data passed on container creation.
4758 * @param rc The VBox error code.
4759 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4760 * @param pszFormat Error message format string.
4761 * @param va Error message arguments.
4762 */
4763/*static*/
4764DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4765 const char *pszFormat, va_list va)
4766{
4767 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4768
4769 Medium *that = static_cast<Medium*>(pvUser);
4770 AssertReturnVoid(that != NULL);
4771
4772 if (that->m->vdError.isEmpty())
4773 that->m->vdError =
4774 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4775 else
4776 that->m->vdError =
4777 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4778 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4779}
4780
4781/**
4782 * PFNVMPROGRESS callback handler for Task operations.
4783 *
4784 * @param uPercent Completetion precentage (0-100).
4785 * @param pvUser Pointer to the Progress instance.
4786 */
4787/*static*/
4788DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4789 void *pvUser)
4790{
4791 Progress *that = static_cast<Progress *>(pvUser);
4792
4793 if (that != NULL)
4794 {
4795 /* update the progress object, capping it at 99% as the final percent
4796 * is used for additional operations like setting the UUIDs and similar. */
4797 HRESULT rc = that->SetCurrentOperationProgress(uPercent * 99 / 100);
4798 if (FAILED(rc))
4799 {
4800 if (rc == E_FAIL)
4801 return VERR_CANCELLED;
4802 else
4803 return VERR_INVALID_STATE;
4804 }
4805 }
4806
4807 return VINF_SUCCESS;
4808}
4809
4810/* static */
4811DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4812 const char * /* pszzValid */)
4813{
4814 Medium *that = static_cast<Medium*>(pvUser);
4815 AssertReturn(that != NULL, false);
4816
4817 /* we always return true since the only keys we have are those found in
4818 * VDBACKENDINFO */
4819 return true;
4820}
4821
4822/* static */
4823DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4824 size_t *pcbValue)
4825{
4826 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4827
4828 Medium *that = static_cast<Medium*>(pvUser);
4829 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4830
4831 Data::PropertyMap::const_iterator it =
4832 that->m->properties.find(Bstr(pszName));
4833 if (it == that->m->properties.end())
4834 return VERR_CFGM_VALUE_NOT_FOUND;
4835
4836 /* we interpret null values as "no value" in Medium */
4837 if (it->second.isEmpty())
4838 return VERR_CFGM_VALUE_NOT_FOUND;
4839
4840 *pcbValue = it->second.length() + 1 /* include terminator */;
4841
4842 return VINF_SUCCESS;
4843}
4844
4845/* static */
4846DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4847 char *pszValue, size_t cchValue)
4848{
4849 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4850
4851 Medium *that = static_cast<Medium*>(pvUser);
4852 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4853
4854 Data::PropertyMap::const_iterator it =
4855 that->m->properties.find(Bstr(pszName));
4856 if (it == that->m->properties.end())
4857 return VERR_CFGM_VALUE_NOT_FOUND;
4858
4859 Utf8Str value = it->second;
4860 if (value.length() >= cchValue)
4861 return VERR_CFGM_NOT_ENOUGH_SPACE;
4862
4863 /* we interpret null values as "no value" in Medium */
4864 if (it->second.isEmpty())
4865 return VERR_CFGM_VALUE_NOT_FOUND;
4866
4867 memcpy(pszValue, value.c_str(), value.length() + 1);
4868
4869 return VINF_SUCCESS;
4870}
4871
4872/**
4873 * Implementation code called from Medium::taskThread for the "create base" task.
4874 *
4875 * This only gets started from Medium::CreateBaseStorage() and always runs
4876 * asynchronously. As a result, we always save the VirtualBox.xml file when we're
4877 * done here.
4878 *
4879 * @param task
4880 * @param pvdOperationIfaces
4881 * @return
4882 */
4883HRESULT Medium::taskThreadCreateBase(Task &task, void *pvdOperationIfaces)
4884{
4885 HRESULT rc = S_OK;
4886
4887 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
4888
4889 /* these parameters we need after creation */
4890 uint64_t size = 0, logicalSize = 0;
4891 bool fGenerateUuid = false;
4892
4893 try
4894 {
4895 /* The lock is also used as a signal from the task initiator (which
4896 * releases it only after RTThreadCreate()) that we can start the job */
4897 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
4898
4899 /* The object may request a specific UUID (through a special form of
4900 * the setLocation() argument). Otherwise we have to generate it */
4901 Guid id = m->id;
4902 fGenerateUuid = id.isEmpty();
4903 if (fGenerateUuid)
4904 {
4905 id.create();
4906 /* VirtualBox::registerHardDisk() will need UUID */
4907 unconst(m->id) = id;
4908 }
4909
4910 PVBOXHDD hdd;
4911 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
4912 ComAssertRCThrow(vrc, E_FAIL);
4913
4914 Utf8Str format(m->strFormat);
4915 Utf8Str location(m->strLocationFull);
4916 /* uint64_t capabilities = */ m->formatObj->capabilities();
4917
4918 /* unlock before the potentially lengthy operation */
4919 Assert(m->state == MediumState_Creating);
4920 thisLock.leave();
4921
4922 try
4923 {
4924 /* ensure the directory exists */
4925 rc = VirtualBox::ensureFilePathExists(location);
4926 if (FAILED(rc)) throw rc;
4927
4928 PDMMEDIAGEOMETRY geo = { 0, 0, 0 }; /* auto-detect */
4929
4930 vrc = VDCreateBase(hdd,
4931 format.c_str(),
4932 location.c_str(),
4933 task.d.size * _1M,
4934 task.d.variant,
4935 NULL,
4936 &geo,
4937 &geo,
4938 id.raw(),
4939 VD_OPEN_FLAGS_NORMAL,
4940 NULL,
4941 vdOperationIfaces);
4942 if (RT_FAILURE(vrc))
4943 {
4944 throw setError(E_FAIL,
4945 tr("Could not create the hard disk storage unit '%s'%s"),
4946 location.raw(), vdError(vrc).raw());
4947 }
4948
4949 size = VDGetFileSize(hdd, 0);
4950 logicalSize = VDGetSize(hdd, 0) / _1M;
4951 }
4952 catch (HRESULT aRC) { rc = aRC; }
4953
4954 VDDestroy(hdd);
4955 }
4956 catch (HRESULT aRC) { rc = aRC; }
4957
4958 if (SUCCEEDED(rc))
4959 {
4960 /* register with mVirtualBox as the last step and move to
4961 * Created state only on success (leaving an orphan file is
4962 * better than breaking media registry consistency) */
4963 bool fNeedsSaveSettings = false;
4964 AutoWriteLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
4965 rc = m->pVirtualBox->registerHardDisk(this, &fNeedsSaveSettings);
4966 treeLock.release();
4967
4968 if (fNeedsSaveSettings)
4969 {
4970 AutoWriteLock vboxlock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
4971 m->pVirtualBox->saveSettings();
4972 }
4973 }
4974
4975 // reenter the lock before changing state
4976 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
4977
4978 if (SUCCEEDED(rc))
4979 {
4980 m->state = MediumState_Created;
4981
4982 m->size = size;
4983 m->logicalSize = logicalSize;
4984 }
4985 else
4986 {
4987 /* back to NotCreated on failure */
4988 m->state = MediumState_NotCreated;
4989
4990 /* reset UUID to prevent it from being reused next time */
4991 if (fGenerateUuid)
4992 unconst(m->id).clear();
4993 }
4994
4995 return rc;
4996}
4997
4998/**
4999 * Implementation code called from Medium::taskThread for the "create diff" task.
5000 *
5001 * This task always gets started from Medium::createDiffStorage() and can run
5002 * synchronously or asynchrously depending on the "wait" parameter passed to that
5003 * function. If we run synchronously, the caller expects the bool *pfNeedsSaveSettings
5004 * to be set before returning; otherwise (in asynchronous mode), we save the settings
5005 * ourselves.
5006 *
5007 * @param task
5008 * @param pvdOperationIfaces
5009 * @return
5010 */
5011HRESULT Medium::taskThreadCreateDiff(Task &task, void *pvdOperationIfaces, bool fIsAsync)
5012{
5013 HRESULT rc = S_OK;
5014
5015 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
5016
5017 bool fNeedsSaveSettings = false;
5018
5019 ComObjPtr<Medium> &pTarget = task.d.target;
5020
5021 uint64_t size = 0, logicalSize = 0;
5022 bool fGenerateUuid = false;
5023
5024 try
5025 {
5026 /* Lock both in {parent,child} order. The lock is also used as a
5027 * signal from the task initiator (which releases it only after
5028 * RTThreadCreate()) that we can start the job*/
5029 AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS);
5030
5031 /* The object may request a specific UUID (through a special form of
5032 * the setLocation() argument). Otherwise we have to generate it */
5033 Guid targetId = pTarget->m->id;
5034 fGenerateUuid = targetId.isEmpty();
5035 if (fGenerateUuid)
5036 {
5037 targetId.create();
5038 /* VirtualBox::registerHardDisk() will need UUID */
5039 unconst(pTarget->m->id) = targetId;
5040 }
5041
5042 PVBOXHDD hdd;
5043 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5044 ComAssertRCThrow(vrc, E_FAIL);
5045
5046 Guid id = m->id;
5047 Utf8Str format(m->strFormat);
5048 Utf8Str location(m->strLocationFull);
5049
5050 Utf8Str targetFormat(pTarget->m->strFormat);
5051 Utf8Str targetLocation(pTarget->m->strLocationFull);
5052
5053 Assert(pTarget->m->state == MediumState_Creating);
5054
5055 /* Note: MediumState_LockedWrite is ok when taking an online
5056 * snapshot */
5057 Assert( m->state == MediumState_LockedRead
5058 || m->state == MediumState_LockedWrite);
5059
5060 /* the two media are now protected by their non-default states;
5061 unlock the media before the potentially lengthy operation */
5062 mediaLock.leave();
5063
5064 try
5065 {
5066 vrc = VDOpen(hdd,
5067 format.c_str(),
5068 location.c_str(),
5069 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5070 m->vdDiskIfaces);
5071 if (RT_FAILURE(vrc))
5072 throw setError(E_FAIL,
5073 tr("Could not open the hard disk storage unit '%s'%s"),
5074 location.raw(), vdError(vrc).raw());
5075
5076 /* ensure the target directory exists */
5077 rc = VirtualBox::ensureFilePathExists(targetLocation);
5078 if (FAILED(rc)) throw rc;
5079
5080 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5081 * be on the safe side. */
5082 vrc = VDCreateDiff(hdd,
5083 targetFormat.c_str(),
5084 targetLocation.c_str(),
5085 task.d.variant,
5086 NULL,
5087 targetId.raw(),
5088 id.raw(),
5089 VD_OPEN_FLAGS_NORMAL,
5090 pTarget->m->vdDiskIfaces,
5091 vdOperationIfaces);
5092 if (RT_FAILURE(vrc))
5093 throw setError(E_FAIL,
5094 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5095 targetLocation.raw(), vdError(vrc).raw());
5096
5097 size = VDGetFileSize(hdd, 1);
5098 logicalSize = VDGetSize(hdd, 1) / _1M;
5099 }
5100 catch (HRESULT aRC) { rc = aRC; }
5101
5102 VDDestroy(hdd);
5103 }
5104 catch (HRESULT aRC) { rc = aRC; }
5105
5106 if (SUCCEEDED(rc))
5107 {
5108 AutoWriteLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
5109
5110 Assert(pTarget->m->pParent.isNull());
5111
5112 /* associate the child with the parent */
5113 pTarget->m->pParent = this;
5114 m->llChildren.push_back(pTarget);
5115
5116 /** @todo r=klaus neither target nor base() are locked,
5117 * potential race! */
5118 /* diffs for immutable hard disks are auto-reset by default */
5119 pTarget->m->autoReset = getBase()->m->type == MediumType_Immutable
5120 ? TRUE
5121 : FALSE;
5122
5123 /* register with mVirtualBox as the last step and move to
5124 * Created state only on success (leaving an orphan file is
5125 * better than breaking media registry consistency) */
5126 rc = m->pVirtualBox->registerHardDisk(pTarget, &fNeedsSaveSettings);
5127
5128 if (FAILED(rc))
5129 /* break the parent association on failure to register */
5130 deparent();
5131 }
5132
5133 AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS);
5134
5135 if (SUCCEEDED(rc))
5136 {
5137 pTarget->m->state = MediumState_Created;
5138
5139 pTarget->m->size = size;
5140 pTarget->m->logicalSize = logicalSize;
5141 }
5142 else
5143 {
5144 /* back to NotCreated on failure */
5145 pTarget->m->state = MediumState_NotCreated;
5146
5147 pTarget->m->autoReset = FALSE;
5148
5149 /* reset UUID to prevent it from being reused next time */
5150 if (fGenerateUuid)
5151 unconst(pTarget->m->id).clear();
5152 }
5153
5154 if (fIsAsync)
5155 {
5156 /* unlock ourselves when done (unless in MediumState_LockedWrite
5157 * state because of taking the online snapshot*/
5158 if (m->state != MediumState_LockedWrite)
5159 {
5160 HRESULT rc2 = UnlockRead(NULL);
5161 AssertComRC(rc2);
5162 }
5163
5164 if (fNeedsSaveSettings)
5165 {
5166 mediaLock.leave();
5167 AutoWriteLock vboxlock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
5168 m->pVirtualBox->saveSettings();
5169 }
5170 }
5171 else
5172 // synchronous mode: report save settings result to caller
5173 if (task.m_pfNeedsSaveSettings)
5174 *task.m_pfNeedsSaveSettings = fNeedsSaveSettings;
5175
5176 /* deregister the task registered in createDiffStorage() */
5177 Assert(m->numCreateDiffTasks != 0);
5178 --m->numCreateDiffTasks;
5179
5180 /* Note that in sync mode, it's the caller's responsibility to
5181 * unlock the hard disk */
5182
5183 return rc;
5184}
5185
5186/**
5187 * Implementation code called from Medium::taskThread for the "merge" task.
5188 *
5189 * This task always gets started from Medium::mergeTo() and can run
5190 * synchronously or asynchrously depending on the "wait" parameter passed to that
5191 * function. If we run synchronously, the caller expects the bool *pfNeedsSaveSettings
5192 * to be set before returning; otherwise (in asynchronous mode), we save the settings
5193 * ourselves.
5194 *
5195 * @param task
5196 * @param pvdOperationIfaces
5197 * @return
5198 */
5199HRESULT Medium::taskThreadMerge(Task &task, void *pvdOperationIfaces, bool fIsAsync)
5200{
5201 HRESULT rc = S_OK;
5202
5203 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
5204
5205 /* The lock is also used as a signal from the task initiator (which
5206 * releases it only after RTThreadCreate()) that we can start the
5207 * job. We don't actually need the lock for anything else since the
5208 * object is protected by MediumState_Deleting and we don't modify
5209 * its sensitive fields below */
5210 {
5211 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5212 }
5213
5214 MergeChain *chain = task.d.chain.get();
5215
5216 try
5217 {
5218 PVBOXHDD hdd;
5219 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5220 ComAssertRCThrow(vrc, E_FAIL);
5221
5222 try
5223 {
5224 /* Open all hard disks in the chain (they are in the
5225 * {parent,child} order in there. Note that we don't lock
5226 * objects in this chain since they must be in states
5227 * (Deleting and LockedWrite) that prevent from changing
5228 * their format and location fields from outside. */
5229
5230 for (MergeChain::const_iterator it = chain->begin();
5231 it != chain->end();
5232 ++it)
5233 {
5234 /* complex sanity (sane complexity) */
5235 Assert((chain->isForward() &&
5236 ((*it != chain->back() &&
5237 (*it)->m->state == MediumState_Deleting) ||
5238 (*it == chain->back() &&
5239 (*it)->m->state == MediumState_LockedWrite))) ||
5240 (!chain->isForward() &&
5241 ((*it != chain->front() &&
5242 (*it)->m->state == MediumState_Deleting) ||
5243 (*it == chain->front() &&
5244 (*it)->m->state == MediumState_LockedWrite))));
5245
5246 Assert(*it == chain->target() ||
5247 (*it)->m->backRefs.size() == 0);
5248
5249 /* open the first image with VDOPEN_FLAGS_INFO because
5250 * it's not necessarily the base one */
5251 vrc = VDOpen(hdd,
5252 (*it)->m->strFormat.c_str(),
5253 (*it)->m->strLocationFull.c_str(),
5254 it == chain->begin() ? VD_OPEN_FLAGS_INFO : 0,
5255 (*it)->m->vdDiskIfaces);
5256 if (RT_FAILURE(vrc))
5257 throw vrc;
5258 }
5259
5260 unsigned start = chain->isForward() ?
5261 0 : (unsigned)chain->size() - 1;
5262 unsigned end = chain->isForward() ?
5263 (unsigned)chain->size() - 1 : 0;
5264
5265 vrc = VDMerge(hdd, start, end, vdOperationIfaces);
5266 if (RT_FAILURE(vrc))
5267 throw vrc;
5268
5269 /* update parent UUIDs */
5270 /// @todo VDMerge should be taught to do so, including the
5271 /// multiple children case
5272 if (chain->isForward())
5273 {
5274 /* target's UUID needs to be updated (note that target
5275 * is the only image in the container on success) */
5276 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5277 if (RT_FAILURE(vrc))
5278 throw vrc;
5279 }
5280 else
5281 {
5282 /* we need to update UUIDs of all source's children
5283 * which cannot be part of the container at once so
5284 * add each one in there individually */
5285 if (chain->children().size() > 0)
5286 {
5287 for (MediaList::const_iterator it = chain->children().begin();
5288 it != chain->children().end();
5289 ++it)
5290 {
5291 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5292 vrc = VDOpen(hdd,
5293 (*it)->m->strFormat.c_str(),
5294 (*it)->m->strLocationFull.c_str(),
5295 VD_OPEN_FLAGS_INFO,
5296 (*it)->m->vdDiskIfaces);
5297 if (RT_FAILURE(vrc))
5298 throw vrc;
5299
5300 vrc = VDSetParentUuid(hdd, 1,
5301 chain->target()->m->id);
5302 if (RT_FAILURE(vrc))
5303 throw vrc;
5304
5305 vrc = VDClose(hdd, false /* fDelete */);
5306 if (RT_FAILURE(vrc))
5307 throw vrc;
5308 }
5309 }
5310 }
5311 }
5312 catch (HRESULT aRC) { rc = aRC; }
5313 catch (int aVRC)
5314 {
5315 throw setError(E_FAIL,
5316 tr("Could not merge the hard disk '%s' to '%s'%s"),
5317 chain->source()->m->strLocationFull.raw(),
5318 chain->target()->m->strLocationFull.raw(),
5319 vdError(aVRC).raw());
5320 }
5321
5322 VDDestroy(hdd);
5323 }
5324 catch (HRESULT aRC) { rc = aRC; }
5325
5326 HRESULT rc2;
5327
5328 if (SUCCEEDED(rc))
5329 {
5330 /* all hard disks but the target were successfully deleted by
5331 * VDMerge; reparent the last one and uninitialize deleted */
5332
5333 AutoWriteLock treeLock(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
5334
5335 Medium *pSource = chain->source();
5336 Medium *pTarget = chain->target();
5337
5338 if (chain->isForward())
5339 {
5340 /* first, unregister the target since it may become a base
5341 * hard disk which needs re-registration */
5342 rc2 = m->pVirtualBox->unregisterHardDisk(pTarget, NULL /*&fNeedsSaveSettings*/);
5343 AssertComRC(rc2);
5344
5345 /* then, reparent it and disconnect the deleted branch at
5346 * both ends (chain->parent() is source's parent) */
5347 pTarget->deparent();
5348 pTarget->m->pParent = chain->parent();
5349 if (pTarget->m->pParent)
5350 {
5351 pTarget->m->pParent->m->llChildren.push_back(pTarget);
5352 pSource->deparent();
5353 }
5354
5355 /* then, register again */
5356 rc2 = m->pVirtualBox->registerHardDisk(pTarget, NULL /*&fNeedsSaveSettings*/);
5357 AssertComRC(rc2);
5358 }
5359 else
5360 {
5361 Assert(pTarget->getChildren().size() == 1);
5362 Medium *targetChild = pTarget->getChildren().front();
5363
5364 /* disconnect the deleted branch at the elder end */
5365 targetChild->deparent();
5366
5367 const MediaList &children = chain->children();
5368
5369 /* reparent source's chidren and disconnect the deleted
5370 * branch at the younger end m*/
5371 if (children.size() > 0)
5372 {
5373 /* obey {parent,child} lock order */
5374 AutoWriteLock sourceLock(pSource COMMA_LOCKVAL_SRC_POS);
5375
5376 for (MediaList::const_iterator it = children.begin();
5377 it != children.end();
5378 ++it)
5379 {
5380 AutoWriteLock childLock(*it COMMA_LOCKVAL_SRC_POS);
5381
5382 Medium *p = *it;
5383 p->deparent(); // removes p from source
5384 pTarget->m->llChildren.push_back(p);
5385 p->m->pParent = pTarget;
5386 }
5387 }
5388 }
5389
5390 /* unregister and uninitialize all hard disks in the chain but the target */
5391 for (MergeChain::iterator it = chain->begin();
5392 it != chain->end();
5393 )
5394 {
5395 if (*it == chain->target())
5396 {
5397 ++it;
5398 continue;
5399 }
5400
5401 rc2 = (*it)->m->pVirtualBox->unregisterHardDisk(*it, NULL /*pfNeedsSaveSettings*/);
5402 AssertComRC(rc2);
5403
5404 /* now, uninitialize the deleted hard disk (note that
5405 * due to the Deleting state, uninit() will not touch
5406 * the parent-child relationship so we need to
5407 * uninitialize each disk individually) */
5408
5409 /* note that the operation initiator hard disk (which is
5410 * normally also the source hard disk) is a special case
5411 * -- there is one more caller added by Task to it which
5412 * we must release. Also, if we are in sync mode, the
5413 * caller may still hold an AutoCaller instance for it
5414 * and therefore we cannot uninit() it (it's therefore
5415 * the caller's responsibility) */
5416 if (*it == this)
5417 task.m_autoCaller.release();
5418
5419 /* release the caller added by MergeChain before uninit() */
5420 (*it)->releaseCaller();
5421
5422 if (fIsAsync || *it != this)
5423 (*it)->uninit();
5424
5425 /* delete (to prevent uninitialization in MergeChain
5426 * dtor) and advance to the next item */
5427 it = chain->erase(it);
5428 }
5429 }
5430
5431 if (fIsAsync)
5432 {
5433 // in asynchronous mode, save settings now
5434 AutoWriteLock vboxlock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
5435 m->pVirtualBox->saveSettings();
5436 }
5437 else
5438 // synchronous mode: report save settings result to caller
5439 if (task.m_pfNeedsSaveSettings)
5440 *task.m_pfNeedsSaveSettings = true;
5441
5442 if (FAILED(rc))
5443 {
5444 /* Here we come if either VDMerge() failed (in which case we
5445 * assume that it tried to do everything to make a further
5446 * retry possible -- e.g. not deleted intermediate hard disks
5447 * and so on) or VirtualBox::saveSettings() failed (where we
5448 * should have the original tree but with intermediate storage
5449 * units deleted by VDMerge()). We have to only restore states
5450 * (through the MergeChain dtor) unless we are run synchronously
5451 * in which case it's the responsibility of the caller as stated
5452 * in the mergeTo() docs. The latter also implies that we
5453 * don't own the merge chain, so release it in this case. */
5454
5455 if (!fIsAsync)
5456 task.d.chain.release();
5457 }
5458
5459 return rc;
5460}
5461
5462/**
5463 * Implementation code called from Medium::taskThread for the "clone" task.
5464 * This only gets started from Medium::CloneTo() and always runs asynchronously.
5465 * As a result, we always save the VirtualBox.xml file when we're done here.
5466 *
5467 * @param task
5468 * @param pvdOperationIfaces
5469 * @return
5470 */
5471HRESULT Medium::taskThreadClone(Task &task, void *pvdOperationIfaces)
5472{
5473 HRESULT rc = S_OK;
5474
5475 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
5476
5477 ComObjPtr<Medium> &pTarget = task.d.target;
5478 ComObjPtr<Medium> &pParent = task.d.parentDisk;
5479
5480 bool fCreatingTarget = false;
5481
5482 uint64_t size = 0, logicalSize = 0;
5483 bool fGenerateUuid = false;
5484
5485 try
5486 {
5487 /* Lock all in {parent,child} order. The lock is also used as a
5488 * signal from the task initiator (which releases it only after
5489 * RTThreadCreate()) that we can start the job. */
5490 AutoMultiWriteLock3 thisLock(this, pTarget, pParent COMMA_LOCKVAL_SRC_POS);
5491
5492 fCreatingTarget = pTarget->m->state == MediumState_Creating;
5493
5494 ImageChain *srcChain = task.d.source.get();
5495 ImageChain *parentChain = task.d.parent.get();
5496
5497 /* The object may request a specific UUID (through a special form of
5498 * the setLocation() argument). Otherwise we have to generate it */
5499 Guid targetId = pTarget->m->id;
5500 fGenerateUuid = targetId.isEmpty();
5501 if (fGenerateUuid)
5502 {
5503 targetId.create();
5504 /* VirtualBox::registerHardDisk() will need UUID */
5505 unconst(pTarget->m->id) = targetId;
5506 }
5507
5508 PVBOXHDD hdd;
5509 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5510 ComAssertRCThrow(vrc, E_FAIL);
5511
5512 try
5513 {
5514 /* Open all hard disk images in the source chain. */
5515 for (MediaList::const_iterator it = srcChain->begin();
5516 it != srcChain->end();
5517 ++it)
5518 {
5519 /* sanity check */
5520 Assert((*it)->m->state == MediumState_LockedRead);
5521
5522 /** Open all images in read-only mode. */
5523 vrc = VDOpen(hdd,
5524 (*it)->m->strFormat.c_str(),
5525 (*it)->m->strLocationFull.c_str(),
5526 VD_OPEN_FLAGS_READONLY,
5527 (*it)->m->vdDiskIfaces);
5528 if (RT_FAILURE(vrc))
5529 throw setError(E_FAIL,
5530 tr("Could not open the hard disk storage unit '%s'%s"),
5531 (*it)->m->strLocationFull.raw(),
5532 vdError(vrc).raw());
5533 }
5534
5535 Utf8Str targetFormat(pTarget->m->strFormat);
5536 Utf8Str targetLocation(pTarget->m->strLocationFull);
5537
5538 Assert( pTarget->m->state == MediumState_Creating
5539 || pTarget->m->state == MediumState_LockedWrite);
5540 Assert(m->state == MediumState_LockedRead);
5541 Assert(pParent.isNull() || pParent->m->state == MediumState_LockedRead);
5542
5543 /* unlock before the potentially lengthy operation */
5544 thisLock.leave();
5545
5546 /* ensure the target directory exists */
5547 rc = VirtualBox::ensureFilePathExists(targetLocation);
5548 if (FAILED(rc)) throw rc;
5549
5550 PVBOXHDD targetHdd;
5551 vrc = VDCreate(m->vdDiskIfaces, &targetHdd);
5552 ComAssertRCThrow(vrc, E_FAIL);
5553
5554 try
5555 {
5556 /* Open all hard disk images in the parent chain. */
5557 for (MediaList::const_iterator it = parentChain->begin();
5558 it != parentChain->end();
5559 ++it)
5560 {
5561 /** @todo r=klaus (*it) is not locked, lots of
5562 * race opportunities below */
5563 /* sanity check */
5564 Assert( (*it)->m->state == MediumState_LockedRead
5565 || (*it)->m->state == MediumState_LockedWrite);
5566
5567 /* Open all images in appropriate mode. */
5568 vrc = VDOpen(targetHdd,
5569 (*it)->m->strFormat.c_str(),
5570 (*it)->m->strLocationFull.c_str(),
5571 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5572 (*it)->m->vdDiskIfaces);
5573 if (RT_FAILURE(vrc))
5574 throw setError(E_FAIL,
5575 tr("Could not open the hard disk storage unit '%s'%s"),
5576 (*it)->m->strLocationFull.raw(),
5577 vdError(vrc).raw());
5578 }
5579
5580 /** @todo r=klaus target isn't locked, race getting the state */
5581 vrc = VDCopy(hdd,
5582 VD_LAST_IMAGE,
5583 targetHdd,
5584 targetFormat.c_str(),
5585 (fCreatingTarget) ? targetLocation.raw() : (char *)NULL,
5586 false,
5587 0,
5588 task.d.variant,
5589 targetId.raw(),
5590 NULL,
5591 pTarget->m->vdDiskIfaces,
5592 vdOperationIfaces);
5593 if (RT_FAILURE(vrc))
5594 throw setError(E_FAIL,
5595 tr("Could not create the clone hard disk '%s'%s"),
5596 targetLocation.raw(), vdError(vrc).raw());
5597
5598 size = VDGetFileSize(targetHdd, 0);
5599 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5600 }
5601 catch (HRESULT aRC) { rc = aRC; }
5602
5603 VDDestroy(targetHdd);
5604 }
5605 catch (HRESULT aRC) { rc = aRC; }
5606
5607 VDDestroy(hdd);
5608 }
5609 catch (HRESULT aRC) { rc = aRC; }
5610
5611 /* Only do the parent changes for newly created images. */
5612 if (SUCCEEDED(rc) && fCreatingTarget)
5613 {
5614 /* we set mParent & children() */
5615 AutoWriteLock alock2(m->pVirtualBox->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
5616
5617 Assert(pTarget->m->pParent.isNull());
5618
5619 if (pParent)
5620 {
5621 /* associate the clone with the parent and deassociate
5622 * from VirtualBox */
5623 pTarget->m->pParent = pParent;
5624 pParent->m->llChildren.push_back(pTarget);
5625
5626 /* register with mVirtualBox as the last step and move to
5627 * Created state only on success (leaving an orphan file is
5628 * better than breaking media registry consistency) */
5629 rc = pParent->m->pVirtualBox->registerHardDisk(pTarget, NULL /* pfNeedsSaveSettings */);
5630
5631 if (FAILED(rc))
5632 /* break parent association on failure to register */
5633 pTarget->deparent(); // removes target from parent
5634 }
5635 else
5636 {
5637 /* just register */
5638 rc = m->pVirtualBox->registerHardDisk(pTarget, NULL /* pfNeedsSaveSettings */);
5639 }
5640 }
5641
5642 if (fCreatingTarget)
5643 {
5644 AutoWriteLock mLock(pTarget COMMA_LOCKVAL_SRC_POS);
5645
5646 if (SUCCEEDED(rc))
5647 {
5648 pTarget->m->state = MediumState_Created;
5649
5650 pTarget->m->size = size;
5651 pTarget->m->logicalSize = logicalSize;
5652 }
5653 else
5654 {
5655 /* back to NotCreated on failure */
5656 pTarget->m->state = MediumState_NotCreated;
5657
5658 /* reset UUID to prevent it from being reused next time */
5659 if (fGenerateUuid)
5660 unconst(pTarget->m->id).clear();
5661 }
5662 }
5663
5664 // now, at the end of this task (always asynchronous), save the settings
5665 {
5666 AutoWriteLock vboxlock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
5667 m->pVirtualBox->saveSettings();
5668 }
5669
5670 /* Everything is explicitly unlocked when the task exits,
5671 * as the task destruction also destroys the source chain. */
5672
5673 /* Make sure the source chain is released early. It could happen
5674 * that we get a deadlock in Appliance::Import when Medium::Close
5675 * is called & the source chain is released at the same time. */
5676 task.d.source.reset();
5677
5678 return rc;
5679}
5680
5681/**
5682 * Implementation code called from Medium::taskThread for the "delete" task.
5683 *
5684 * This task always gets started from Medium::deleteStorage() and can run
5685 * synchronously or asynchrously depending on the "wait" parameter passed to that
5686 * function.
5687 *
5688 * @return
5689 */
5690HRESULT Medium::taskThreadDelete()
5691{
5692 HRESULT rc = S_OK;
5693
5694 try
5695 {
5696 /* The lock is also used as a signal from the task initiator (which
5697 * releases it only after RTThreadCreate()) that we can start the job */
5698 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5699
5700 PVBOXHDD hdd;
5701 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5702 ComAssertRCThrow(vrc, E_FAIL);
5703
5704 Utf8Str format(m->strFormat);
5705 Utf8Str location(m->strLocationFull);
5706
5707 /* unlock before the potentially lengthy operation */
5708 Assert(m->state == MediumState_Deleting);
5709 thisLock.release();
5710
5711 try
5712 {
5713 vrc = VDOpen(hdd,
5714 format.c_str(),
5715 location.c_str(),
5716 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5717 m->vdDiskIfaces);
5718 if (RT_SUCCESS(vrc))
5719 vrc = VDClose(hdd, true /* fDelete */);
5720
5721 if (RT_FAILURE(vrc))
5722 throw setError(E_FAIL,
5723 tr("Could not delete the hard disk storage unit '%s'%s"),
5724 location.raw(), vdError(vrc).raw());
5725
5726 }
5727 catch (HRESULT aRC) { rc = aRC; }
5728
5729 VDDestroy(hdd);
5730 }
5731 catch (HRESULT aRC) { rc = aRC; }
5732
5733 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5734
5735 /* go to the NotCreated state even on failure since the storage
5736 * may have been already partially deleted and cannot be used any
5737 * more. One will be able to manually re-open the storage if really
5738 * needed to re-register it. */
5739 m->state = MediumState_NotCreated;
5740
5741 /* Reset UUID to prevent Create* from reusing it again */
5742 unconst(m->id).clear();
5743
5744 return rc;
5745}
5746
5747/**
5748 * Implementation code called from Medium::taskThread for the "reset" task.
5749 *
5750 * This always gets started asynchronously from Medium::Reset().
5751 *
5752 * @param task
5753 * @param pvdOperationIfaces
5754 * @return
5755 */
5756HRESULT Medium::taskThreadReset(void *pvdOperationIfaces, bool fIsAsync)
5757{
5758 HRESULT rc = S_OK;
5759
5760 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
5761
5762 uint64_t size = 0, logicalSize = 0;
5763
5764 try
5765 {
5766 /* The lock is also used as a signal from the task initiator (which
5767 * releases it only after RTThreadCreate()) that we can start the job */
5768 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5769
5770 /// @todo Below we use a pair of delete/create operations to reset
5771 /// the diff contents but the most efficient way will of course be
5772 /// to add a VDResetDiff() API call
5773
5774 PVBOXHDD hdd;
5775 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5776 ComAssertRCThrow(vrc, E_FAIL);
5777
5778 Guid id = m->id;
5779 Utf8Str format(m->strFormat);
5780 Utf8Str location(m->strLocationFull);
5781
5782 Medium *pParent = m->pParent;
5783 Guid parentId = pParent->m->id;
5784 Utf8Str parentFormat(pParent->m->strFormat);
5785 Utf8Str parentLocation(pParent->m->strLocationFull);
5786
5787 Assert(m->state == MediumState_LockedWrite);
5788
5789 /* unlock before the potentially lengthy operation */
5790 thisLock.release();
5791
5792 try
5793 {
5794 /* first, delete the storage unit */
5795 vrc = VDOpen(hdd,
5796 format.c_str(),
5797 location.c_str(),
5798 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5799 m->vdDiskIfaces);
5800 if (RT_SUCCESS(vrc))
5801 vrc = VDClose(hdd, true /* fDelete */);
5802
5803 if (RT_FAILURE(vrc))
5804 throw setError(E_FAIL,
5805 tr("Could not delete the hard disk storage unit '%s'%s"),
5806 location.raw(), vdError(vrc).raw());
5807
5808 /* next, create it again */
5809 vrc = VDOpen(hdd,
5810 parentFormat.c_str(),
5811 parentLocation.c_str(),
5812 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5813 m->vdDiskIfaces);
5814 if (RT_FAILURE(vrc))
5815 throw setError(E_FAIL,
5816 tr("Could not open the hard disk storage unit '%s'%s"),
5817 parentLocation.raw(), vdError(vrc).raw());
5818
5819 vrc = VDCreateDiff(hdd,
5820 format.c_str(),
5821 location.c_str(),
5822 /// @todo use the same image variant as before
5823 VD_IMAGE_FLAGS_NONE,
5824 NULL,
5825 id.raw(),
5826 parentId.raw(),
5827 VD_OPEN_FLAGS_NORMAL,
5828 m->vdDiskIfaces,
5829 vdOperationIfaces);
5830 if (RT_FAILURE(vrc))
5831 throw setError(E_FAIL,
5832 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5833 location.raw(), vdError(vrc).raw());
5834
5835 size = VDGetFileSize(hdd, 1);
5836 logicalSize = VDGetSize(hdd, 1) / _1M;
5837 }
5838 catch (HRESULT aRC) { rc = aRC; }
5839
5840 VDDestroy(hdd);
5841 }
5842 catch (HRESULT aRC) { rc = aRC; }
5843
5844 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5845
5846 m->size = size;
5847 m->logicalSize = logicalSize;
5848
5849 if (fIsAsync)
5850 {
5851 /* unlock ourselves when done */
5852 HRESULT rc2 = UnlockWrite(NULL);
5853 AssertComRC(rc2);
5854 }
5855
5856 /* Note that in sync mode, it's the caller's responsibility to
5857 * unlock the hard disk */
5858
5859 return rc;
5860}
5861
5862/**
5863 * Implementation code called from Medium::taskThread for the "compact" task.
5864 * @param pvdOperationIfaces
5865 * @return
5866 */
5867HRESULT Medium::taskThreadCompact(Task &task, void *pvdOperationIfaces)
5868{
5869 HRESULT rc = S_OK;
5870
5871 PVDINTERFACE vdOperationIfaces = (PVDINTERFACE)pvdOperationIfaces;
5872
5873 /* Lock all in {parent,child} order. The lock is also used as a
5874 * signal from the task initiator (which releases it only after
5875 * RTThreadCreate()) that we can start the job. */
5876 AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
5877
5878 ImageChain *imgChain = task.d.images.get();
5879
5880 try
5881 {
5882 PVBOXHDD hdd;
5883 int vrc = VDCreate(m->vdDiskIfaces, &hdd);
5884 ComAssertRCThrow(vrc, E_FAIL);
5885
5886 try
5887 {
5888 /* Open all hard disk images in the chain. */
5889 MediaList::const_iterator last = imgChain->end();
5890 last--;
5891 for (MediaList::const_iterator it = imgChain->begin();
5892 it != imgChain->end();
5893 ++it)
5894 {
5895 /* sanity check */
5896 if (it == last)
5897 Assert((*it)->m->state == MediumState_LockedWrite);
5898 else
5899 Assert((*it)->m->state == MediumState_LockedRead);
5900
5901 /** Open all images but last in read-only mode. */
5902 vrc = VDOpen(hdd,
5903 (*it)->m->strFormat.c_str(),
5904 (*it)->m->strLocationFull.c_str(),
5905 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5906 (*it)->m->vdDiskIfaces);
5907 if (RT_FAILURE(vrc))
5908 throw setError(E_FAIL,
5909 tr("Could not open the hard disk storage unit '%s'%s"),
5910 (*it)->m->strLocationFull.raw(),
5911 vdError(vrc).raw());
5912 }
5913
5914 Assert(m->state == MediumState_LockedWrite);
5915
5916 Utf8Str location(m->strLocationFull);
5917
5918 /* unlock before the potentially lengthy operation */
5919 thisLock.leave();
5920
5921 vrc = VDCompact(hdd, VD_LAST_IMAGE, vdOperationIfaces);
5922 if (RT_FAILURE(vrc))
5923 {
5924 if (vrc == VERR_NOT_SUPPORTED)
5925 throw setError(VBOX_E_NOT_SUPPORTED,
5926 tr("Compacting is not yet supported for hard disk '%s'"),
5927 location.raw());
5928 else if (vrc == VERR_NOT_IMPLEMENTED)
5929 throw setError(E_NOTIMPL,
5930 tr("Compacting is not implemented, hard disk '%s'"),
5931 location.raw());
5932 else
5933 throw setError(E_FAIL,
5934 tr("Could not compact hard disk '%s'%s"),
5935 location.raw(),
5936 vdError(vrc).raw());
5937 }
5938 }
5939 catch (HRESULT aRC) { rc = aRC; }
5940
5941 VDDestroy(hdd);
5942 }
5943 catch (HRESULT aRC) { rc = aRC; }
5944
5945 /* Everything is explicitly unlocked when the task exits,
5946 * as the task destruction also destroys the image chain. */
5947
5948 return rc;
5949}
5950
5951
5952/**
5953 * Thread function for time-consuming tasks.
5954 *
5955 * The Task structure passed to @a pvUser must be allocated using new and will
5956 * be freed by this method before it returns.
5957 *
5958 * @param pvUser Pointer to the Task instance.
5959 */
5960/* static */
5961DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
5962{
5963 std::auto_ptr<Task> task(static_cast<Task*>(pvUser));
5964 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
5965
5966 bool fIsAsync = thread != NIL_RTTHREAD;
5967
5968 Medium *that = task->that;
5969
5970 /* Set up a per-operation progress interface, can be used freely (for
5971 * binary operations you can use it either on the source or target). */
5972 VDINTERFACEPROGRESS vdIfCallsProgress;
5973 vdIfCallsProgress.cbSize = sizeof(VDINTERFACEPROGRESS);
5974 vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
5975 vdIfCallsProgress.pfnProgress = Medium::vdProgressCall;
5976 VDINTERFACE vdIfProgress;
5977 PVDINTERFACE vdOperationIfaces = NULL;
5978 int vrc1 = VDInterfaceAdd(&vdIfProgress,
5979 "Medium::vdInterfaceProgress",
5980 VDINTERFACETYPE_PROGRESS,
5981 &vdIfCallsProgress,
5982 task->m_pProgress,
5983 &vdOperationIfaces);
5984 AssertRCReturn(vrc1, E_FAIL);
5985
5986 /* Note: no need in AutoCaller because Task does that */
5987
5988 LogFlowFuncEnter();
5989 LogFlowFunc(("{%p}: operation=%d\n", that, task->m_operation));
5990
5991 HRESULT rc = S_OK;
5992
5993 switch (task->m_operation)
5994 {
5995 ////////////////////////////////////////////////////////////////////////
5996
5997 case Task::CreateBase:
5998 rc = that->taskThreadCreateBase(*task, (void*)vdOperationIfaces);
5999 break;
6000
6001 case Task::CreateDiff:
6002 rc = that->taskThreadCreateDiff(*task, (void*)vdOperationIfaces, fIsAsync);
6003 break;
6004
6005 case Task::Merge:
6006 rc = that->taskThreadMerge(*task, (void*)vdOperationIfaces, fIsAsync);
6007 break;
6008
6009 case Task::Clone:
6010 rc = that->taskThreadClone(*task, (void*)vdOperationIfaces);
6011 break;
6012
6013 case Task::Delete:
6014 rc = that->taskThreadDelete();
6015 break;
6016
6017 case Task::Reset:
6018 rc = that->taskThreadReset((void*)vdOperationIfaces, fIsAsync);
6019 break;
6020
6021 case Task::Compact:
6022 rc = that->taskThreadCompact(*task, (void*)vdOperationIfaces);
6023 break;
6024
6025 default:
6026 AssertFailedReturn(VERR_GENERAL_FAILURE);
6027 }
6028
6029 /* complete the progress if run asynchronously */
6030 if (fIsAsync)
6031 {
6032 if (!task->m_pProgress.isNull())
6033 task->m_pProgress->notifyComplete(rc);
6034 }
6035 else
6036 {
6037 task->m_rc = rc;
6038 }
6039
6040 LogFlowFunc(("rc=%Rhrc\n", rc));
6041 LogFlowFuncLeave();
6042
6043 return VINF_SUCCESS;
6044
6045 /// @todo ugly hack, fix ComAssert... later
6046 #undef setError
6047}
6048
6049/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

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