VirtualBox

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

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

Main: more header cleanup; move VirtualBox instance data to cpp implementation file

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