VirtualBox

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

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

Main: remove separate snapshots tree lock, have snapshots list use machine lock instead

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