VirtualBox

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

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

Main: fix regressions with renaming VMs; as a welcome side effect, Machine::saveSettings() no longer implicitly calls VirtualBox::saveSettings() and therefore no longer requires the global VirtualBox lock

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