VirtualBox

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

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

Snapshots: Fix corruption during merge of snapshots

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