VirtualBox

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

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

Main: fix deleteSnapshot() which was broken for immutable and writethrough attachments

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette