VirtualBox

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

Last change on this file since 25414 was 25310, checked in by vboxsync, 14 years ago

Main: lock validator, first batch: implement per-thread stack to trace locking (disabled by default, use VBOX_WITH_LOCK_VALIDATOR, but that WILL FAIL presently)

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

© 2023 Oracle
ContactPrivacy policyTerms of Use