VirtualBox

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

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

Backed out 55600; unable to create new VMs

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

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