VirtualBox

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

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

Main/Medium: when locking a medium for reading/writing, wait for concurrent queryInfo() call, as it locks the medium as well. Causes races when trying to lock the all media when starting a VM.

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

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