VirtualBox

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

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

Main: split up huge Medium task thread function, part 4 (done)

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