VirtualBox

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

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

whitespace

  • 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 23451 2009-09-30 18:59:14Z 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 && !m->hostDrive))
3371 {
3372 Guid id;
3373
3374 Utf8Str location(aLocation);
3375
3376 if (m->state == MediumState_NotCreated)
3377 {
3378 /* must be a file (formatObj must be already known) */
3379 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3380
3381 if (RTPathFilename(location.c_str()) == NULL)
3382 {
3383 /* no file name is given (either an empty string or ends with a
3384 * slash), generate a new UUID + file name if the state allows
3385 * this */
3386
3387 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3388 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3389 E_FAIL);
3390
3391 Bstr ext = m->formatObj->fileExtensions().front();
3392 ComAssertMsgRet(!ext.isEmpty(),
3393 ("Default extension must not be empty\n"),
3394 E_FAIL);
3395
3396 id.create();
3397
3398 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3399 location.raw(), id.raw(), ext.raw());
3400 }
3401 }
3402
3403 /* append the default folder if no path is given */
3404 if (!RTPathHavePath(location.c_str()))
3405 location = Utf8StrFmt("%s%c%s",
3406 mVirtualBox->getDefaultHardDiskFolder().raw(),
3407 RTPATH_DELIMITER,
3408 location.raw());
3409
3410 /* get the full file name */
3411 Utf8Str locationFull;
3412 int vrc = mVirtualBox->calculateFullPath(location, locationFull);
3413 if (RT_FAILURE(vrc))
3414 return setError(VBOX_E_FILE_ERROR,
3415 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3416 location.raw(), vrc);
3417
3418 /* detect the backend from the storage unit if importing */
3419 if (isImport)
3420 {
3421 char *backendName = NULL;
3422
3423 /* is it a file? */
3424 {
3425 RTFILE file;
3426 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ);
3427 if (RT_SUCCESS(vrc))
3428 RTFileClose(file);
3429 }
3430 if (RT_SUCCESS(vrc))
3431 {
3432 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3433 }
3434 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3435 {
3436 /* assume it's not a file, restore the original location */
3437 location = locationFull = aLocation;
3438 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3439 }
3440
3441 if (RT_FAILURE(vrc))
3442 {
3443 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3444 return setError(VBOX_E_FILE_ERROR,
3445 tr("Could not find file for the hard disk '%s' (%Rrc)"),
3446 locationFull.raw(), vrc);
3447 else if (aFormat.isEmpty())
3448 return setError(VBOX_E_IPRT_ERROR,
3449 tr("Could not get the storage format of the hard disk '%s' (%Rrc)"),
3450 locationFull.raw(), vrc);
3451 else
3452 {
3453 HRESULT rc = setFormat(Bstr(aFormat));
3454 /* setFormat() must not fail since we've just used the backend so
3455 * the format object must be there */
3456 AssertComRCReturnRC(rc);
3457 }
3458 }
3459 else
3460 {
3461 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3462
3463 HRESULT rc = setFormat(Bstr(backendName));
3464 RTStrFree(backendName);
3465
3466 /* setFormat() must not fail since we've just used the backend so
3467 * the format object must be there */
3468 AssertComRCReturnRC(rc);
3469 }
3470 }
3471
3472 /* is it still a file? */
3473 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3474 {
3475 m->location = location;
3476 m->locationFull = locationFull;
3477
3478 if (m->state == MediumState_NotCreated)
3479 {
3480 /* assign a new UUID (this UUID will be used when calling
3481 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3482 * also do that if we didn't generate it to make sure it is
3483 * either generated by us or reset to null */
3484 unconst(m->id) = id;
3485 }
3486 }
3487 else
3488 {
3489 m->location = locationFull;
3490 m->locationFull = locationFull;
3491 }
3492 }
3493 else
3494 {
3495 m->location = aLocation;
3496 m->locationFull = aLocation;
3497 }
3498
3499 return S_OK;
3500}
3501
3502/**
3503 * Queries information from the image file.
3504 *
3505 * As a result of this call, the accessibility state and data members such as
3506 * size and description will be updated with the current information.
3507 *
3508 * @note This method may block during a system I/O call that checks storage
3509 * accessibility.
3510 *
3511 * @note Locks treeLock() for reading and writing (for new diff media checked
3512 * for the first time). Locks mParent for reading. Locks this object for
3513 * writing.
3514 */
3515HRESULT Medium::queryInfo()
3516{
3517 AutoWriteLock alock(this);
3518
3519 AssertReturn(m->state == MediumState_Created ||
3520 m->state == MediumState_Inaccessible ||
3521 m->state == MediumState_LockedRead ||
3522 m->state == MediumState_LockedWrite,
3523 E_FAIL);
3524
3525 HRESULT rc = S_OK;
3526
3527 int vrc = VINF_SUCCESS;
3528
3529 /* check if a blocking queryInfo() call is in progress on some other thread,
3530 * and wait for it to finish if so instead of querying data ourselves */
3531 if (m->queryInfoSem != NIL_RTSEMEVENTMULTI)
3532 {
3533 Assert(m->state == MediumState_LockedRead);
3534
3535 ++m->queryInfoCallers;
3536 alock.leave();
3537
3538 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3539
3540 alock.enter();
3541 --m->queryInfoCallers;
3542
3543 if (m->queryInfoCallers == 0)
3544 {
3545 /* last waiting caller deletes the semaphore */
3546 RTSemEventMultiDestroy(m->queryInfoSem);
3547 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3548 }
3549
3550 AssertRC(vrc);
3551
3552 return S_OK;
3553 }
3554
3555 /* lazily create a semaphore for possible callers */
3556 vrc = RTSemEventMultiCreate(&m->queryInfoSem);
3557 ComAssertRCRet(vrc, E_FAIL);
3558
3559 bool tempStateSet = false;
3560 if (m->state != MediumState_LockedRead &&
3561 m->state != MediumState_LockedWrite)
3562 {
3563 /* Cause other methods to prevent any modifications before leaving the
3564 * lock. Note that clients will never see this temporary state change
3565 * since any COMGETTER(State) is (or will be) blocked until we finish
3566 * and restore the actual state. */
3567 m->state = MediumState_LockedRead;
3568 tempStateSet = true;
3569 }
3570
3571 /* leave the lock before a blocking operation */
3572 alock.leave();
3573
3574 bool success = false;
3575 Utf8Str lastAccessError;
3576
3577 try
3578 {
3579 Utf8Str location(m->locationFull);
3580
3581 /* totally useless to do accessibility checks for host drives */
3582 if (m->hostDrive)
3583 {
3584 success = true;
3585 throw S_OK;
3586 }
3587
3588 /* are we dealing with a new hard disk constructed using the existing
3589 * location? */
3590 bool isImport = m->id.isEmpty();
3591
3592 PVBOXHDD hdd;
3593 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3594 ComAssertRCThrow(vrc, E_FAIL);
3595
3596 try
3597 {
3598 unsigned flags = VD_OPEN_FLAGS_INFO;
3599
3600 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3601 * hard disks because that would prevent necessary modifications
3602 * when opening hard disks of some third-party formats for the first
3603 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3604 * generate an UUID if it is missing) */
3605 if ( (m->hddOpenMode == OpenReadOnly)
3606 || !isImport
3607 )
3608 flags |= VD_OPEN_FLAGS_READONLY;
3609
3610 /** @todo This kind of opening of images is assuming that diff
3611 * images can be opened as base images. Should be fixed ASAP. */
3612 vrc = VDOpen(hdd,
3613 Utf8Str(m->format).c_str(),
3614 location.c_str(),
3615 flags,
3616 m->vdDiskIfaces);
3617 if (RT_FAILURE(vrc))
3618 {
3619 lastAccessError = Utf8StrFmt(tr("Could not open the hard disk '%ls'%s"),
3620 m->locationFull.raw(), vdError(vrc).raw());
3621 throw S_OK;
3622 }
3623
3624 if (m->formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3625 {
3626 /* modify the UUIDs if necessary */
3627 if (m->setImageId)
3628 {
3629 vrc = VDSetUuid(hdd, 0, m->imageId);
3630 ComAssertRCThrow(vrc, E_FAIL);
3631 }
3632 if (m->setParentId)
3633 {
3634 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3635 ComAssertRCThrow(vrc, E_FAIL);
3636 }
3637 /* zap the information, these are no long-term members */
3638 m->setImageId = false;
3639 unconst(m->imageId).clear();
3640 m->setParentId = false;
3641 unconst(m->parentId).clear();
3642
3643 /* check the UUID */
3644 RTUUID uuid;
3645 vrc = VDGetUuid(hdd, 0, &uuid);
3646 ComAssertRCThrow(vrc, E_FAIL);
3647
3648 if (isImport)
3649 {
3650 unconst(m->id) = uuid;
3651
3652 if (m->id.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3653 // only when importing a VDMK that has no UUID, create one in memory
3654 unconst(m->id).create();
3655 }
3656 else
3657 {
3658 Assert(!m->id.isEmpty());
3659
3660 if (m->id != uuid)
3661 {
3662 lastAccessError = Utf8StrFmt(
3663 tr("UUID {%RTuuid} of the hard disk '%ls' does not match the value {%RTuuid} stored in the media registry ('%ls')"),
3664 &uuid, m->locationFull.raw(), m->id.raw(),
3665 mVirtualBox->settingsFilePath().raw());
3666 throw S_OK;
3667 }
3668 }
3669 }
3670 else
3671 {
3672 /* the backend does not support storing UUIDs within the
3673 * underlying storage so use what we store in XML */
3674
3675 /* generate an UUID for an imported UUID-less hard disk */
3676 if (isImport)
3677 {
3678 if (m->setImageId)
3679 unconst(m->id) = m->imageId;
3680 else
3681 unconst(m->id).create();
3682 }
3683 }
3684
3685 /* check the type */
3686 unsigned uImageFlags;
3687 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3688 ComAssertRCThrow(vrc, E_FAIL);
3689
3690 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3691 {
3692 RTUUID parentId;
3693 vrc = VDGetParentUuid(hdd, 0, &parentId);
3694 ComAssertRCThrow(vrc, E_FAIL);
3695
3696 if (isImport)
3697 {
3698 /* the parent must be known to us. Note that we freely
3699 * call locking methods of mVirtualBox and parent from the
3700 * write lock (breaking the {parent,child} lock order)
3701 * because there may be no concurrent access to the just
3702 * opened hard disk on ther threads yet (and init() will
3703 * fail if this method reporst MediumState_Inaccessible) */
3704
3705 Guid id = parentId;
3706 ComObjPtr<Medium> parent;
3707 rc = mVirtualBox->findHardDisk(&id, NULL,
3708 false /* aSetError */,
3709 &parent);
3710 if (FAILED(rc))
3711 {
3712 lastAccessError = Utf8StrFmt(
3713 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%ls' is not found in the media registry ('%ls')"),
3714 &parentId, m->locationFull.raw(),
3715 mVirtualBox->settingsFilePath().raw());
3716 throw S_OK;
3717 }
3718
3719 /* deassociate from VirtualBox, associate with parent */
3720
3721 mVirtualBox->removeDependentChild(this);
3722
3723 /* we set mParent & children() */
3724 AutoWriteLock treeLock(this->treeLock());
3725
3726 Assert(mParent.isNull());
3727 mParent = parent;
3728 mParent->addDependentChild(this);
3729 }
3730 else
3731 {
3732 /* we access mParent */
3733 AutoReadLock treeLock(this->treeLock());
3734
3735 /* check that parent UUIDs match. Note that there's no need
3736 * for the parent's AutoCaller (our lifetime is bound to
3737 * it) */
3738
3739 if (mParent.isNull())
3740 {
3741 lastAccessError = Utf8StrFmt(
3742 tr("Hard disk '%ls' is differencing but it is not associated with any parent hard disk in the media registry ('%ls')"),
3743 m->locationFull.raw(),
3744 mVirtualBox->settingsFilePath().raw());
3745 throw S_OK;
3746 }
3747
3748 AutoReadLock parentLock(mParent);
3749 if (mParent->state() != MediumState_Inaccessible &&
3750 mParent->id() != parentId)
3751 {
3752 lastAccessError = Utf8StrFmt(
3753 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')"),
3754 &parentId, m->locationFull.raw(),
3755 mParent->id().raw(),
3756 mVirtualBox->settingsFilePath().raw());
3757 throw S_OK;
3758 }
3759
3760 /// @todo NEWMEDIA what to do if the parent is not
3761 /// accessible while the diff is? Probably, nothing. The
3762 /// real code will detect the mismatch anyway.
3763 }
3764 }
3765
3766 m->size = VDGetFileSize(hdd, 0);
3767 m->logicalSize = VDGetSize(hdd, 0) / _1M;
3768
3769 success = true;
3770 }
3771 catch (HRESULT aRC)
3772 {
3773 rc = aRC;
3774 }
3775
3776 VDDestroy(hdd);
3777
3778 }
3779 catch (HRESULT aRC)
3780 {
3781 rc = aRC;
3782 }
3783
3784 alock.enter();
3785
3786 if (success)
3787 m->lastAccessError.setNull();
3788 else
3789 {
3790 m->lastAccessError = lastAccessError;
3791 LogWarningFunc(("'%ls' is not accessible (error='%ls', rc=%Rhrc, vrc=%Rrc)\n",
3792 m->locationFull.raw(), m->lastAccessError.raw(),
3793 rc, vrc));
3794 }
3795
3796 /* inform other callers if there are any */
3797 if (m->queryInfoCallers > 0)
3798 {
3799 RTSemEventMultiSignal(m->queryInfoSem);
3800 }
3801 else
3802 {
3803 /* delete the semaphore ourselves */
3804 RTSemEventMultiDestroy(m->queryInfoSem);
3805 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3806 }
3807
3808 if (tempStateSet)
3809 {
3810 /* Set the proper state according to the result of the check */
3811 if (success)
3812 m->state = MediumState_Created;
3813 else
3814 m->state = MediumState_Inaccessible;
3815 }
3816 else
3817 {
3818 /* we're locked, use a special field to store the result */
3819 m->accessibleInLock = success;
3820 }
3821
3822 return rc;
3823}
3824
3825/**
3826 * Sets the extended error info according to the current media state.
3827 *
3828 * @note Must be called from under this object's write or read lock.
3829 */
3830HRESULT Medium::setStateError()
3831{
3832 HRESULT rc = E_FAIL;
3833
3834 switch (m->state)
3835 {
3836 case MediumState_NotCreated:
3837 {
3838 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3839 tr("Storage for the medium '%ls' is not created"),
3840 m->locationFull.raw());
3841 break;
3842 }
3843 case MediumState_Created:
3844 {
3845 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3846 tr("Storage for the medium '%ls' is already created"),
3847 m->locationFull.raw());
3848 break;
3849 }
3850 case MediumState_LockedRead:
3851 {
3852 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3853 tr("Medium '%ls' is locked for reading by another task"),
3854 m->locationFull.raw());
3855 break;
3856 }
3857 case MediumState_LockedWrite:
3858 {
3859 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3860 tr("Medium '%ls' is locked for writing by another task"),
3861 m->locationFull.raw());
3862 break;
3863 }
3864 case MediumState_Inaccessible:
3865 {
3866 AssertMsg(!m->lastAccessError.isEmpty(),
3867 ("There must always be a reason for Inaccessible"));
3868
3869 /* be in sync with Console::powerUpThread() */
3870 if (!m->lastAccessError.isEmpty())
3871 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3872 tr("Medium '%ls' is not accessible. %ls"),
3873 m->locationFull.raw(), m->lastAccessError.raw());
3874 else
3875 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3876 tr("Medium '%ls' is not accessible"),
3877 m->locationFull.raw());
3878 break;
3879 }
3880 case MediumState_Creating:
3881 {
3882 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3883 tr("Storage for the medium '%ls' is being created"),
3884 m->locationFull.raw(), m->lastAccessError.raw());
3885 break;
3886 }
3887 case MediumState_Deleting:
3888 {
3889 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3890 tr("Storage for the medium '%ls' is being deleted"),
3891 m->locationFull.raw(), m->lastAccessError.raw());
3892 break;
3893 }
3894 default:
3895 {
3896 AssertFailed();
3897 break;
3898 }
3899 }
3900
3901 return rc;
3902}
3903
3904/**
3905 * Deletes the hard disk storage unit.
3906 *
3907 * If @a aProgress is not NULL but the object it points to is @c null then a new
3908 * progress object will be created and assigned to @a *aProgress on success,
3909 * otherwise the existing progress object is used. If Progress is NULL, then no
3910 * progress object is created/used at all.
3911 *
3912 * When @a aWait is @c false, this method will create a thread to perform the
3913 * delete operation asynchronously and will return immediately. Otherwise, it
3914 * will perform the operation on the calling thread and will not return to the
3915 * caller until the operation is completed. Note that @a aProgress cannot be
3916 * NULL when @a aWait is @c false (this method will assert in this case).
3917 *
3918 * @param aProgress Where to find/store a Progress object to track operation
3919 * completion.
3920 * @param aWait @c true if this method should block instead of creating
3921 * an asynchronous thread.
3922 *
3923 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
3924 * writing.
3925 */
3926HRESULT Medium::deleteStorage(ComObjPtr <Progress> *aProgress, bool aWait)
3927{
3928 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
3929
3930 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
3931 * ourselves atomically after detecting that deletion is possible to make
3932 * sure that we don't do that after another thread has done
3933 * VirtualBox::findHardDisk() but before it starts using us (provided that
3934 * it holds a mVirtualBox lock too of course). */
3935
3936 AutoWriteLock vboxLock(mVirtualBox);
3937
3938 AutoWriteLock alock(this);
3939
3940 if (!(m->formatObj->capabilities() &
3941 (MediumFormatCapabilities_CreateDynamic |
3942 MediumFormatCapabilities_CreateFixed)))
3943 return setError(VBOX_E_NOT_SUPPORTED,
3944 tr("Hard disk format '%ls' does not support storage deletion"),
3945 m->format.raw());
3946
3947 /* Note that we are fine with Inaccessible state too: a) for symmetry with
3948 * create calls and b) because it doesn't really harm to try, if it is
3949 * really inaccessibke, the delete operation will fail anyway. Accepting
3950 * Inaccessible state is especially important because all registered hard
3951 * disks are initially Inaccessible upon VBoxSVC startup until
3952 * COMGETTER(State) is called. */
3953
3954 switch (m->state)
3955 {
3956 case MediumState_Created:
3957 case MediumState_Inaccessible:
3958 break;
3959 default:
3960 return setStateError();
3961 }
3962
3963 if (m->backRefs.size() != 0)
3964 return setError(VBOX_E_OBJECT_IN_USE,
3965 tr("Hard disk '%ls' is attached to %d virtual machines"),
3966 m->locationFull.raw(), m->backRefs.size());
3967
3968 HRESULT rc = canClose();
3969 CheckComRCReturnRC(rc);
3970
3971 /* go to Deleting state before leaving the lock */
3972 m->state = MediumState_Deleting;
3973
3974 /* we need to leave this object's write lock now because of
3975 * unregisterWithVirtualBox() that locks treeLock() for writing */
3976 alock.leave();
3977
3978 /* try to remove from the list of known hard disks before performing actual
3979 * deletion (we favor the consistency of the media registry in the first
3980 * place which would have been broken if unregisterWithVirtualBox() failed
3981 * after we successfully deleted the storage) */
3982
3983 rc = unregisterWithVirtualBox();
3984
3985 alock.enter();
3986
3987 /* restore the state because we may fail below; we will set it later again*/
3988 m->state = MediumState_Created;
3989
3990 CheckComRCReturnRC(rc);
3991
3992 ComObjPtr <Progress> progress;
3993
3994 if (aProgress != NULL)
3995 {
3996 /* use the existing progress object... */
3997 progress = *aProgress;
3998
3999 /* ...but create a new one if it is null */
4000 if (progress.isNull())
4001 {
4002 progress.createObject();
4003 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4004 BstrFmt(tr("Deleting hard disk storage unit '%ls'"),
4005 m->locationFull.raw()),
4006 FALSE /* aCancelable */);
4007 CheckComRCReturnRC(rc);
4008 }
4009 }
4010
4011 std::auto_ptr <Task> task(new Task(this, progress, Task::Delete));
4012 AssertComRCReturnRC(task->autoCaller.rc());
4013
4014 if (aWait)
4015 {
4016 /* go to Deleting state before starting the task */
4017 m->state = MediumState_Deleting;
4018
4019 rc = task->runNow();
4020 }
4021 else
4022 {
4023 rc = task->startThread();
4024 CheckComRCReturnRC(rc);
4025
4026 /* go to Deleting state before leaving the lock */
4027 m->state = MediumState_Deleting;
4028 }
4029
4030 /* task is now owned (or already deleted) by taskThread() so release it */
4031 task.release();
4032
4033 if (aProgress != NULL)
4034 {
4035 /* return progress to the caller */
4036 *aProgress = progress;
4037 }
4038
4039 return rc;
4040}
4041
4042/**
4043 * Creates a new differencing storage unit using the given target hard disk's
4044 * format and the location. Note that @c aTarget must be NotCreated.
4045 *
4046 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4047 * this hard disk for reading assuming that the caller has already done so. This
4048 * is used when taking an online snaopshot (where all original hard disks are
4049 * locked for writing and must remain such). Note however that if @a aWait is
4050 * @c false and this method returns a success then the thread started by
4051 * this method will unlock the hard disk (unless it is in
4052 * MediumState_LockedWrite state) so make sure the hard disk is either in
4053 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4054 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4055 * make sure you do it yourself as needed.
4056 *
4057 * If @a aProgress is not NULL but the object it points to is @c null then a new
4058 * progress object will be created and assigned to @a *aProgress on success,
4059 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4060 * progress object is created/used at all.
4061 *
4062 * When @a aWait is @c false, this method will create a thread to perform the
4063 * create operation asynchronously and will return immediately. Otherwise, it
4064 * will perform the operation on the calling thread and will not return to the
4065 * caller until the operation is completed. Note that @a aProgress cannot be
4066 * NULL when @a aWait is @c false (this method will assert in this case).
4067 *
4068 * @param aTarget Target hard disk.
4069 * @param aVariant Precise image variant to create.
4070 * @param aProgress Where to find/store a Progress object to track operation
4071 * completion.
4072 * @param aWait @c true if this method should block instead of creating
4073 * an asynchronous thread.
4074 *
4075 * @note Locks this object and @a aTarget for writing.
4076 */
4077HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4078 MediumVariant_T aVariant,
4079 ComObjPtr<Progress> *aProgress,
4080 bool aWait)
4081{
4082 AssertReturn(!aTarget.isNull(), E_FAIL);
4083 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4084
4085 AutoCaller autoCaller(this);
4086 CheckComRCReturnRC(autoCaller.rc());
4087
4088 AutoCaller targetCaller(aTarget);
4089 CheckComRCReturnRC(targetCaller.rc());
4090
4091 AutoMultiWriteLock2 alock(this, aTarget);
4092
4093 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4094
4095 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4096 AssertReturn(m->state == MediumState_LockedRead ||
4097 m->state == MediumState_LockedWrite, E_FAIL);
4098
4099 if (aTarget->m->state != MediumState_NotCreated)
4100 return aTarget->setStateError();
4101
4102 HRESULT rc = S_OK;
4103
4104 /* check that the hard disk is not attached to any VM in the current state*/
4105 for (BackRefList::const_iterator it = m->backRefs.begin();
4106 it != m->backRefs.end(); ++ it)
4107 {
4108 if (it->inCurState)
4109 {
4110 /* Note: when a VM snapshot is being taken, all normal hard disks
4111 * attached to the VM in the current state will be, as an exception,
4112 * also associated with the snapshot which is about to create (see
4113 * SnapshotMachine::init()) before deassociating them from the
4114 * current state (which takes place only on success in
4115 * Machine::fixupHardDisks()), so that the size of snapshotIds
4116 * will be 1 in this case. The given condition is used to filter out
4117 * this legal situatinon and do not report an error. */
4118
4119 if (it->snapshotIds.size() == 0)
4120 {
4121 return setError(VBOX_E_INVALID_OBJECT_STATE,
4122 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"),
4123 m->locationFull.raw(), it->machineId.raw());
4124 }
4125
4126 Assert(it->snapshotIds.size() == 1);
4127 }
4128 }
4129
4130 ComObjPtr <Progress> progress;
4131
4132 if (aProgress != NULL)
4133 {
4134 /* use the existing progress object... */
4135 progress = *aProgress;
4136
4137 /* ...but create a new one if it is null */
4138 if (progress.isNull())
4139 {
4140 progress.createObject();
4141 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4142 BstrFmt(tr("Creating differencing hard disk storage unit '%ls'"),
4143 aTarget->m->locationFull.raw()),
4144 TRUE /* aCancelable */);
4145 CheckComRCReturnRC(rc);
4146 }
4147 }
4148
4149 /* setup task object and thread to carry out the operation
4150 * asynchronously */
4151
4152 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateDiff));
4153 AssertComRCReturnRC(task->autoCaller.rc());
4154
4155 task->setData(aTarget);
4156 task->d.variant = aVariant;
4157
4158 /* register a task (it will deregister itself when done) */
4159 ++m->numCreateDiffTasks;
4160 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4161
4162 if (aWait)
4163 {
4164 /* go to Creating state before starting the task */
4165 aTarget->m->state = MediumState_Creating;
4166
4167 rc = task->runNow();
4168 }
4169 else
4170 {
4171 rc = task->startThread();
4172 CheckComRCReturnRC(rc);
4173
4174 /* go to Creating state before leaving the lock */
4175 aTarget->m->state = MediumState_Creating;
4176 }
4177
4178 /* task is now owned (or already deleted) by taskThread() so release it */
4179 task.release();
4180
4181 if (aProgress != NULL)
4182 {
4183 /* return progress to the caller */
4184 *aProgress = progress;
4185 }
4186
4187 return rc;
4188}
4189
4190/**
4191 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4192 * disks for the merge operation.
4193 *
4194 * This method is to be called prior to calling the #mergeTo() to perform
4195 * necessary consistency checks and place involved hard disks to appropriate
4196 * states. If #mergeTo() is not called or fails, the state modifications
4197 * performed by this method must be undone by #cancelMergeTo().
4198 *
4199 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4200 * responsibility to detach the source and all intermediate hard disks before
4201 * calling #mergeTo() (which will fail otherwise).
4202 *
4203 * See #mergeTo() for more information about merging.
4204 *
4205 * @param aTarget Target hard disk.
4206 * @param aChain Where to store the created merge chain.
4207 * @param aIgnoreAttachments Don't check if the source or any intermediate
4208 * hard disk is attached to any VM.
4209 *
4210 * @note Locks treeLock() for reading. Locks this object, aTarget and all
4211 * intermediate hard disks for writing.
4212 */
4213HRESULT Medium::prepareMergeTo(Medium *aTarget,
4214 MergeChain * &aChain,
4215 bool aIgnoreAttachments /*= false*/)
4216{
4217 AssertReturn(aTarget != NULL, E_FAIL);
4218
4219 AutoCaller autoCaller(this);
4220 AssertComRCReturnRC(autoCaller.rc());
4221
4222 AutoCaller targetCaller(aTarget);
4223 AssertComRCReturnRC(targetCaller.rc());
4224
4225 aChain = NULL;
4226
4227 /* we walk the tree */
4228 AutoReadLock treeLock(this->treeLock());
4229
4230 HRESULT rc = S_OK;
4231
4232 /* detect the merge direction */
4233 bool forward;
4234 {
4235 Medium *parent = mParent;
4236 while (parent != NULL && parent != aTarget)
4237 parent = parent->mParent;
4238 if (parent == aTarget)
4239 forward = false;
4240 else
4241 {
4242 parent = aTarget->mParent;
4243 while (parent != NULL && parent != this)
4244 parent = parent->mParent;
4245 if (parent == this)
4246 forward = true;
4247 else
4248 {
4249 Bstr tgtLoc;
4250 {
4251 AutoReadLock alock(this);
4252 tgtLoc = aTarget->locationFull();
4253 }
4254
4255 AutoReadLock alock(this);
4256 return setError(E_FAIL,
4257 tr("Hard disks '%ls' and '%ls' are unrelated"),
4258 m->locationFull.raw(), tgtLoc.raw());
4259 }
4260 }
4261 }
4262
4263 /* build the chain (will do necessary checks and state changes) */
4264 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4265 aIgnoreAttachments));
4266 {
4267 Medium *last = forward ? aTarget : this;
4268 Medium *first = forward ? this : aTarget;
4269
4270 for (;;)
4271 {
4272 if (last == aTarget)
4273 rc = chain->addTarget(last);
4274 else if (last == this)
4275 rc = chain->addSource(last);
4276 else
4277 rc = chain->addIntermediate(last);
4278 CheckComRCReturnRC(rc);
4279
4280 if (last == first)
4281 break;
4282
4283 last = last->mParent;
4284 }
4285 }
4286
4287 aChain = chain.release();
4288
4289 return S_OK;
4290}
4291
4292/**
4293 * Merges this hard disk to the specified hard disk which must be either its
4294 * direct ancestor or descendant.
4295 *
4296 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4297 * get two varians of the merge operation:
4298 *
4299 * forward merge
4300 * ------------------------->
4301 * [Extra] <- SOURCE <- Intermediate <- TARGET
4302 * Any Del Del LockWr
4303 *
4304 *
4305 * backward merge
4306 * <-------------------------
4307 * TARGET <- Intermediate <- SOURCE <- [Extra]
4308 * LockWr Del Del LockWr
4309 *
4310 * Each scheme shows the involved hard disks on the hard disk chain where
4311 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4312 * the hard disk must have at a time of the mergeTo() call.
4313 *
4314 * The hard disks in the square braces may be absent (e.g. when the forward
4315 * operation takes place and SOURCE is the base hard disk, or when the backward
4316 * merge operation takes place and TARGET is the last child in the chain) but if
4317 * they present they are involved too as shown.
4318 *
4319 * Nor the source hard disk neither intermediate hard disks may be attached to
4320 * any VM directly or in the snapshot, otherwise this method will assert.
4321 *
4322 * The #prepareMergeTo() method must be called prior to this method to place all
4323 * involved to necessary states and perform other consistency checks.
4324 *
4325 * If @a aWait is @c true then this method will perform the operation on the
4326 * calling thread and will not return to the caller until the operation is
4327 * completed. When this method succeeds, all intermediate hard disk objects in
4328 * the chain will be uninitialized, the state of the target hard disk (and all
4329 * involved extra hard disks) will be restored and @a aChain will be deleted.
4330 * Note that this (source) hard disk is not uninitialized because of possible
4331 * AutoCaller instances held by the caller of this method on the current thread.
4332 * It's therefore the responsibility of the caller to call Medium::uninit()
4333 * after releasing all callers in this case!
4334 *
4335 * If @a aWait is @c false then this method will crea,te a thread to perform the
4336 * create operation asynchronously and will return immediately. If the operation
4337 * succeeds, the thread will uninitialize the source hard disk object and all
4338 * intermediate hard disk objects in the chain, reset the state of the target
4339 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4340 * operation fails, the thread will only reset the states of all involved hard
4341 * disks and delete @a aChain.
4342 *
4343 * When this method fails (regardless of the @a aWait mode), it is a caller's
4344 * responsiblity to undo state changes and delete @a aChain using
4345 * #cancelMergeTo().
4346 *
4347 * If @a aProgress is not NULL but the object it points to is @c null then a new
4348 * progress object will be created and assigned to @a *aProgress on success,
4349 * otherwise the existing progress object is used. If Progress is NULL, then no
4350 * progress object is created/used at all. Note that @a aProgress cannot be
4351 * NULL when @a aWait is @c false (this method will assert in this case).
4352 *
4353 * @param aChain Merge chain created by #prepareMergeTo().
4354 * @param aProgress Where to find/store a Progress object to track operation
4355 * completion.
4356 * @param aWait @c true if this method should block instead of creating
4357 * an asynchronous thread.
4358 *
4359 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4360 * for writing.
4361 */
4362HRESULT Medium::mergeTo(MergeChain *aChain,
4363 ComObjPtr <Progress> *aProgress,
4364 bool aWait)
4365{
4366 AssertReturn(aChain != NULL, E_FAIL);
4367 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4368
4369 AutoCaller autoCaller(this);
4370 CheckComRCReturnRC(autoCaller.rc());
4371
4372 HRESULT rc = S_OK;
4373
4374 ComObjPtr <Progress> progress;
4375
4376 if (aProgress != NULL)
4377 {
4378 /* use the existing progress object... */
4379 progress = *aProgress;
4380
4381 /* ...but create a new one if it is null */
4382 if (progress.isNull())
4383 {
4384 AutoReadLock alock(this);
4385
4386 progress.createObject();
4387 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4388 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4389 name().raw(), aChain->target()->name().raw()),
4390 TRUE /* aCancelable */);
4391 CheckComRCReturnRC(rc);
4392 }
4393 }
4394
4395 /* setup task object and thread to carry out the operation
4396 * asynchronously */
4397
4398 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4399 AssertComRCReturnRC(task->autoCaller.rc());
4400
4401 task->setData(aChain);
4402
4403 /* Note: task owns aChain (will delete it when not needed) in all cases
4404 * except when @a aWait is @c true and runNow() fails -- in this case
4405 * aChain will be left away because cancelMergeTo() will be applied by the
4406 * caller on it as it is required in the documentation above */
4407
4408 if (aWait)
4409 {
4410 rc = task->runNow();
4411 }
4412 else
4413 {
4414 rc = task->startThread();
4415 CheckComRCReturnRC(rc);
4416 }
4417
4418 /* task is now owned (or already deleted) by taskThread() so release it */
4419 task.release();
4420
4421 if (aProgress != NULL)
4422 {
4423 /* return progress to the caller */
4424 *aProgress = progress;
4425 }
4426
4427 return rc;
4428}
4429
4430/**
4431 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4432 * or fails. Frees memory occupied by @a aChain.
4433 *
4434 * @param aChain Merge chain created by #prepareMergeTo().
4435 *
4436 * @note Locks the hard disks from the chain for writing.
4437 */
4438void Medium::cancelMergeTo(MergeChain *aChain)
4439{
4440 AutoCaller autoCaller(this);
4441 AssertComRCReturnVoid(autoCaller.rc());
4442
4443 AssertReturnVoid(aChain != NULL);
4444
4445 /* the destructor will do the thing */
4446 delete aChain;
4447}
4448
4449/**
4450 * Initializes the image medium object by opening an image file at the specified
4451 * location.
4452 *
4453 * @param aVirtualBox Parent VirtualBox object.
4454 * @param aLocation Path to the image file (can be relative to the
4455 * VirtualBox home directory).
4456 * @param aId UUID of the image.
4457 */
4458HRESULT Medium::protectedInit(VirtualBox *aVirtualBox, CBSTR aLocation,
4459 const Guid &aId)
4460{
4461 LogFlowThisFunc(("aLocation='%ls', aId={%RTuuid}\n", aLocation, aId.raw()));
4462
4463 AssertReturn(aVirtualBox, E_INVALIDARG);
4464 AssertReturn(aLocation, E_INVALIDARG);
4465 AssertReturn(!aId.isEmpty(), E_INVALIDARG);
4466
4467 /* Enclose the state transition NotReady->InInit->Ready */
4468 AutoInitSpan autoInitSpan(this);
4469 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4470
4471 HRESULT rc = S_OK;
4472
4473 /* share parent weakly */
4474 unconst(mVirtualBox) = aVirtualBox;
4475
4476 /* register with parent early, since uninit() will unconditionally
4477 * unregister on failure */
4478 mVirtualBox->addDependentChild(this);
4479
4480 /* there must be a storage unit */
4481 m->state = MediumState_Created;
4482
4483 unconst(m->id) = aId;
4484 rc = setLocation(aLocation);
4485 CheckComRCReturnRC(rc);
4486
4487 LogFlowThisFunc(("m->locationFull='%ls'\n", m->locationFull.raw()));
4488
4489 /* get all the information about the medium from the file */
4490 rc = queryInfo();
4491
4492 if (SUCCEEDED(rc))
4493 {
4494 /* if the image file is not accessible, it's not acceptable for the
4495 * newly opened media so convert this into an error */
4496 if (!m->lastAccessError.isEmpty())
4497 rc = setError(VBOX_E_FILE_ERROR, Utf8Str(m->lastAccessError).c_str());
4498 }
4499
4500 /* Confirm a successful initialization when it's the case */
4501 if (SUCCEEDED(rc))
4502 autoInitSpan.setSucceeded();
4503
4504 return rc;
4505}
4506
4507/**
4508 * Initializes the image medium object by loading its data from the given
4509 * settings node.
4510 *
4511 * Note that it is assumed that this method is called only for registered media.
4512 *
4513 * @param aVirtualBox Parent VirtualBox object.
4514 * @param aImageNode Either <DVDImage> or <FloppyImage> settings node.
4515 */
4516HRESULT Medium::protectedInit(VirtualBox *aVirtualBox,
4517 const settings::Medium &data)
4518{
4519 AssertReturn(aVirtualBox, E_INVALIDARG);
4520
4521 /* Enclose the state transition NotReady->InInit->Ready */
4522 AutoInitSpan autoInitSpan(this);
4523 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4524
4525 HRESULT rc = S_OK;
4526
4527 /* share parent weakly */
4528 unconst(mVirtualBox) = aVirtualBox;
4529
4530 /* register with parent early, since uninit() will unconditionally
4531 * unregister on failure */
4532 mVirtualBox->addDependentChild(this);
4533
4534 /* see below why we don't call queryInfo() (and therefore treat the medium
4535 * as inaccessible for now */
4536 m->state = MediumState_Inaccessible;
4537
4538 /* required */
4539 unconst(m->id) = data.uuid;
4540 /* required */
4541 rc = setLocation(data.strLocation);
4542 CheckComRCReturnRC(rc);
4543
4544 m->description = data.strDescription;
4545
4546 LogFlowThisFunc(("m->locationFull='%ls', m->id={%RTuuid}\n",
4547 m->locationFull.raw(), m->id.raw()));
4548
4549 /* Don't call queryInfo() for registered media to prevent the calling
4550 * thread (i.e. the VirtualBox server startup thread) from an unexpected
4551 * freeze but mark it as initially inaccessible instead. The vital UUID and
4552 * location properties are read from the registry file above; to get the
4553 * actual state and the rest of the data, the user will have to call
4554 * COMGETTER(State).*/
4555
4556 /* Confirm a successful initialization when it's the case */
4557 if (SUCCEEDED(rc))
4558 autoInitSpan.setSucceeded();
4559
4560 return rc;
4561}
4562
4563/**
4564 * Uninitializes the instance.
4565 *
4566 * Called either from FinalRelease() or by the parent when it gets destroyed.
4567 */
4568void Medium::protectedUninit()
4569{
4570 LogFlowThisFunc(("\n"));
4571
4572 /* Enclose the state transition Ready->InUninit->NotReady */
4573 AutoUninitSpan autoUninitSpan(this);
4574 if (autoUninitSpan.uninitDone())
4575 return;
4576
4577 mVirtualBox->removeDependentChild(this);
4578
4579 unconst(mVirtualBox).setNull();
4580}
4581
4582// private methods
4583////////////////////////////////////////////////////////////////////////////////
4584
4585/**
4586 * Checks that the format ID is valid and sets it on success.
4587 *
4588 * Note that this method will caller-reference the format object on success!
4589 * This reference must be released somewhere to let the MediumFormat object be
4590 * uninitialized.
4591 *
4592 * @note Must be called from under this object's write lock.
4593 */
4594HRESULT Medium::setFormat(CBSTR aFormat)
4595{
4596 /* get the format object first */
4597 {
4598 AutoReadLock propsLock(mVirtualBox->systemProperties());
4599
4600 unconst(m->formatObj)
4601 = mVirtualBox->systemProperties()->mediumFormat(aFormat);
4602 if (m->formatObj.isNull())
4603 return setError(E_INVALIDARG,
4604 tr("Invalid hard disk storage format '%ls'"), aFormat);
4605
4606 /* reference the format permanently to prevent its unexpected
4607 * uninitialization */
4608 HRESULT rc = m->formatObj->addCaller();
4609 AssertComRCReturnRC(rc);
4610
4611 /* get properties (preinsert them as keys in the map). Note that the
4612 * map doesn't grow over the object life time since the set of
4613 * properties is meant to be constant. */
4614
4615 Assert(m->properties.empty());
4616
4617 for (MediumFormat::PropertyList::const_iterator it =
4618 m->formatObj->properties().begin();
4619 it != m->formatObj->properties().end();
4620 ++ it)
4621 {
4622 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4623 }
4624 }
4625
4626 unconst(m->format) = aFormat;
4627
4628 return S_OK;
4629}
4630
4631/**
4632 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
4633 * write lock.
4634 *
4635 * @note Also reused by Medium::Reset().
4636 *
4637 * @note Locks treeLock() for reading.
4638 */
4639HRESULT Medium::canClose()
4640{
4641 /* we access children */
4642 AutoReadLock treeLock(this->treeLock());
4643
4644 if (children().size() != 0)
4645 return setError(E_FAIL,
4646 tr("Hard disk '%ls' has %d child hard disks"),
4647 children().size());
4648
4649 return S_OK;
4650}
4651
4652/**
4653 * @note Called from within this object's AutoWriteLock.
4654 */
4655HRESULT Medium::canAttach(const Guid & /* aMachineId */,
4656 const Guid & /* aSnapshotId */)
4657{
4658 if (m->numCreateDiffTasks > 0)
4659 return setError(E_FAIL,
4660 tr("One or more differencing child hard disks are being created for the hard disk '%ls' (%u)"),
4661 m->locationFull.raw(), m->numCreateDiffTasks);
4662
4663 return S_OK;
4664}
4665
4666/**
4667 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
4668 * from under mVirtualBox write lock.
4669 *
4670 * @note Locks treeLock() for writing.
4671 */
4672HRESULT Medium::unregisterWithVirtualBox()
4673{
4674 /* Note that we need to de-associate ourselves from the parent to let
4675 * unregisterHardDisk() properly save the registry */
4676
4677 /* we modify mParent and access children */
4678 AutoWriteLock treeLock(this->treeLock());
4679
4680 const ComObjPtr<Medium, ComWeakRef> parent = mParent;
4681
4682 AssertReturn(children().size() == 0, E_FAIL);
4683
4684 if (!mParent.isNull())
4685 {
4686 /* deassociate from the parent, associate with VirtualBox */
4687 mVirtualBox->addDependentChild(this);
4688 mParent->removeDependentChild(this);
4689 mParent.setNull();
4690 }
4691
4692 HRESULT rc = mVirtualBox->unregisterHardDisk(this);
4693
4694 if (FAILED(rc))
4695 {
4696 if (!parent.isNull())
4697 {
4698 /* re-associate with the parent as we are still relatives in the
4699 * registry */
4700 mParent = parent;
4701 mParent->addDependentChild(this);
4702 mVirtualBox->removeDependentChild(this);
4703 }
4704 }
4705
4706 return rc;
4707}
4708
4709/**
4710 * Returns the last error message collected by the vdErrorCall callback and
4711 * resets it.
4712 *
4713 * The error message is returned prepended with a dot and a space, like this:
4714 * <code>
4715 * ". <error_text> (%Rrc)"
4716 * </code>
4717 * to make it easily appendable to a more general error message. The @c %Rrc
4718 * format string is given @a aVRC as an argument.
4719 *
4720 * If there is no last error message collected by vdErrorCall or if it is a
4721 * null or empty string, then this function returns the following text:
4722 * <code>
4723 * " (%Rrc)"
4724 * </code>
4725 *
4726 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4727 * the callback isn't called by more than one thread at a time.
4728 *
4729 * @param aVRC VBox error code to use when no error message is provided.
4730 */
4731Utf8Str Medium::vdError(int aVRC)
4732{
4733 Utf8Str error;
4734
4735 if (m->vdError.isEmpty())
4736 error = Utf8StrFmt(" (%Rrc)", aVRC);
4737 else
4738 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4739
4740 m->vdError.setNull();
4741
4742 return error;
4743}
4744
4745/**
4746 * Error message callback.
4747 *
4748 * Puts the reported error message to the m->vdError field.
4749 *
4750 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4751 * the callback isn't called by more than one thread at a time.
4752 *
4753 * @param pvUser The opaque data passed on container creation.
4754 * @param rc The VBox error code.
4755 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4756 * @param pszFormat Error message format string.
4757 * @param va Error message arguments.
4758 */
4759/*static*/
4760DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4761 const char *pszFormat, va_list va)
4762{
4763 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4764
4765 Medium *that = static_cast<Medium*>(pvUser);
4766 AssertReturnVoid(that != NULL);
4767
4768 if (that->m->vdError.isEmpty())
4769 that->m->vdError =
4770 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4771 else
4772 that->m->vdError =
4773 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4774 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4775}
4776
4777/**
4778 * PFNVMPROGRESS callback handler for Task operations.
4779 *
4780 * @param uPercent Completetion precentage (0-100).
4781 * @param pvUser Pointer to the Progress instance.
4782 */
4783/*static*/
4784DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4785 void *pvUser)
4786{
4787 Medium *that = static_cast<Medium*>(pvUser);
4788 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4789
4790 if (that->m->vdProgress != NULL)
4791 {
4792 /* update the progress object, capping it at 99% as the final percent
4793 * is used for additional operations like setting the UUIDs and similar. */
4794 HRESULT rc = that->m->vdProgress->SetCurrentOperationProgress(uPercent * 99 / 100);
4795 if (FAILED(rc))
4796 {
4797 if (rc == E_FAIL)
4798 return VERR_CANCELLED;
4799 else
4800 return VERR_INVALID_STATE;
4801 }
4802 }
4803
4804 return VINF_SUCCESS;
4805}
4806
4807/* static */
4808DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4809 const char * /* pszzValid */)
4810{
4811 Medium *that = static_cast<Medium*>(pvUser);
4812 AssertReturn(that != NULL, false);
4813
4814 /* we always return true since the only keys we have are those found in
4815 * VDBACKENDINFO */
4816 return true;
4817}
4818
4819/* static */
4820DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4821 size_t *pcbValue)
4822{
4823 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4824
4825 Medium *that = static_cast<Medium*>(pvUser);
4826 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4827
4828 Data::PropertyMap::const_iterator it =
4829 that->m->properties.find(Bstr(pszName));
4830 if (it == that->m->properties.end())
4831 return VERR_CFGM_VALUE_NOT_FOUND;
4832
4833 /* we interpret null values as "no value" in Medium */
4834 if (it->second.isNull())
4835 return VERR_CFGM_VALUE_NOT_FOUND;
4836
4837 *pcbValue = it->second.length() + 1 /* include terminator */;
4838
4839 return VINF_SUCCESS;
4840}
4841
4842/* static */
4843DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4844 char *pszValue, size_t cchValue)
4845{
4846 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4847
4848 Medium *that = static_cast<Medium*>(pvUser);
4849 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4850
4851 Data::PropertyMap::const_iterator it =
4852 that->m->properties.find(Bstr(pszName));
4853 if (it == that->m->properties.end())
4854 return VERR_CFGM_VALUE_NOT_FOUND;
4855
4856 Utf8Str value = it->second;
4857 if (value.length() >= cchValue)
4858 return VERR_CFGM_NOT_ENOUGH_SPACE;
4859
4860 /* we interpret null values as "no value" in Medium */
4861 if (it->second.isNull())
4862 return VERR_CFGM_VALUE_NOT_FOUND;
4863
4864 memcpy(pszValue, value.c_str(), value.length() + 1);
4865
4866 return VINF_SUCCESS;
4867}
4868
4869/**
4870 * Thread function for time-consuming tasks.
4871 *
4872 * The Task structure passed to @a pvUser must be allocated using new and will
4873 * be freed by this method before it returns.
4874 *
4875 * @param pvUser Pointer to the Task instance.
4876 */
4877/* static */
4878DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
4879{
4880 std::auto_ptr <Task> task(static_cast <Task *>(pvUser));
4881 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
4882
4883 bool isAsync = thread != NIL_RTTHREAD;
4884
4885 Medium *that = task->that;
4886
4887 /// @todo ugly hack, fix ComAssert... later
4888 #define setError that->setError
4889
4890 /* Note: no need in AutoCaller because Task does that */
4891
4892 LogFlowFuncEnter();
4893 LogFlowFunc(("{%p}: operation=%d\n", that, task->operation));
4894
4895 HRESULT rc = S_OK;
4896
4897 switch (task->operation)
4898 {
4899 ////////////////////////////////////////////////////////////////////////
4900
4901 case Task::CreateBase:
4902 {
4903 /* The lock is also used as a signal from the task initiator (which
4904 * releases it only after RTThreadCreate()) that we can start the job */
4905 AutoWriteLock thatLock(that);
4906
4907 /* these parameters we need after creation */
4908 uint64_t size = 0, logicalSize = 0;
4909
4910 /* The object may request a specific UUID (through a special form of
4911 * the setLocation() argument). Otherwise we have to generate it */
4912 Guid id = that->m->id;
4913 bool generateUuid = id.isEmpty();
4914 if (generateUuid)
4915 {
4916 id.create();
4917 /* VirtualBox::registerHardDisk() will need UUID */
4918 unconst(that->m->id) = id;
4919 }
4920
4921 try
4922 {
4923 PVBOXHDD hdd;
4924 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
4925 ComAssertRCThrow(vrc, E_FAIL);
4926
4927 Utf8Str format(that->m->format);
4928 Utf8Str location(that->m->locationFull);
4929 /* uint64_t capabilities = */ that->m->formatObj->capabilities();
4930
4931 /* unlock before the potentially lengthy operation */
4932 Assert(that->m->state == MediumState_Creating);
4933 thatLock.leave();
4934
4935 try
4936 {
4937 /* ensure the directory exists */
4938 rc = VirtualBox::ensureFilePathExists(location);
4939 CheckComRCThrowRC(rc);
4940
4941 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
4942
4943 /* needed for vdProgressCallback */
4944 that->m->vdProgress = task->progress;
4945
4946 vrc = VDCreateBase(hdd, format.c_str(), location.c_str(),
4947 task->d.size * _1M,
4948 task->d.variant,
4949 NULL, &geo, &geo, id.raw(),
4950 VD_OPEN_FLAGS_NORMAL,
4951 NULL, that->m->vdDiskIfaces);
4952
4953 if (RT_FAILURE(vrc))
4954 {
4955 throw setError(E_FAIL,
4956 tr("Could not create the hard disk storage unit '%s'%s"),
4957 location.raw(), that->vdError(vrc).raw());
4958 }
4959
4960 size = VDGetFileSize(hdd, 0);
4961 logicalSize = VDGetSize(hdd, 0) / _1M;
4962 }
4963 catch (HRESULT aRC) { rc = aRC; }
4964
4965 VDDestroy(hdd);
4966 }
4967 catch (HRESULT aRC) { rc = aRC; }
4968
4969 if (SUCCEEDED(rc))
4970 {
4971 /* register with mVirtualBox as the last step and move to
4972 * Created state only on success (leaving an orphan file is
4973 * better than breaking media registry consistency) */
4974 rc = that->mVirtualBox->registerHardDisk(that);
4975 }
4976
4977 thatLock.maybeEnter();
4978
4979 if (SUCCEEDED(rc))
4980 {
4981 that->m->state = MediumState_Created;
4982
4983 that->m->size = size;
4984 that->m->logicalSize = logicalSize;
4985 }
4986 else
4987 {
4988 /* back to NotCreated on failure */
4989 that->m->state = MediumState_NotCreated;
4990
4991 /* reset UUID to prevent it from being reused next time */
4992 if (generateUuid)
4993 unconst(that->m->id).clear();
4994 }
4995
4996 break;
4997 }
4998
4999 ////////////////////////////////////////////////////////////////////////
5000
5001 case Task::CreateDiff:
5002 {
5003 ComObjPtr<Medium> &target = task->d.target;
5004
5005 /* Lock both in {parent,child} order. The lock is also used as a
5006 * signal from the task initiator (which releases it only after
5007 * RTThreadCreate()) that we can start the job*/
5008 AutoMultiWriteLock2 thatLock(that, target);
5009
5010 uint64_t size = 0, logicalSize = 0;
5011
5012 /* The object may request a specific UUID (through a special form of
5013 * the setLocation() argument). Otherwise we have to generate it */
5014 Guid targetId = target->m->id;
5015 bool generateUuid = targetId.isEmpty();
5016 if (generateUuid)
5017 {
5018 targetId.create();
5019 /* VirtualBox::registerHardDisk() will need UUID */
5020 unconst(target->m->id) = targetId;
5021 }
5022
5023 try
5024 {
5025 PVBOXHDD hdd;
5026 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5027 ComAssertRCThrow(vrc, E_FAIL);
5028
5029 Guid id = that->m->id;
5030 Utf8Str format(that->m->format);
5031 Utf8Str location(that->m->locationFull);
5032
5033 Utf8Str targetFormat(target->m->format);
5034 Utf8Str targetLocation(target->m->locationFull);
5035
5036 Assert(target->m->state == MediumState_Creating);
5037
5038 /* Note: MediumState_LockedWrite is ok when taking an online
5039 * snapshot */
5040 Assert(that->m->state == MediumState_LockedRead ||
5041 that->m->state == MediumState_LockedWrite);
5042
5043 /* unlock before the potentially lengthy operation */
5044 thatLock.leave();
5045
5046 try
5047 {
5048 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5049 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5050 that->m->vdDiskIfaces);
5051 if (RT_FAILURE(vrc))
5052 {
5053 throw setError(E_FAIL,
5054 tr("Could not open the hard disk storage unit '%s'%s"),
5055 location.raw(), that->vdError(vrc).raw());
5056 }
5057
5058 /* ensure the target directory exists */
5059 rc = VirtualBox::ensureFilePathExists(targetLocation);
5060 CheckComRCThrowRC(rc);
5061
5062 /* needed for vdProgressCallback */
5063 that->m->vdProgress = task->progress;
5064
5065 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5066 * be on the safe side. */
5067 vrc = VDCreateDiff(hdd, targetFormat.c_str(),
5068 targetLocation.c_str(),
5069 task->d.variant,
5070 NULL, targetId.raw(),
5071 id.raw(),
5072 VD_OPEN_FLAGS_NORMAL,
5073 target->m->vdDiskIfaces,
5074 that->m->vdDiskIfaces);
5075
5076 that->m->vdProgress = NULL;
5077
5078 if (RT_FAILURE(vrc))
5079 {
5080 throw setError(E_FAIL,
5081 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5082 targetLocation.raw(), that->vdError(vrc).raw());
5083 }
5084
5085 size = VDGetFileSize(hdd, 1);
5086 logicalSize = VDGetSize(hdd, 1) / _1M;
5087 }
5088 catch (HRESULT aRC) { rc = aRC; }
5089
5090 VDDestroy(hdd);
5091 }
5092 catch (HRESULT aRC) { rc = aRC; }
5093
5094 if (SUCCEEDED(rc))
5095 {
5096 /* we set mParent & children() (note that thatLock is released
5097 * here), but lock VirtualBox first to follow the rule */
5098 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5099 that->treeLock());
5100
5101 Assert(target->mParent.isNull());
5102
5103 /* associate the child with the parent and deassociate from
5104 * VirtualBox */
5105 target->mParent = that;
5106 that->addDependentChild(target);
5107 target->mVirtualBox->removeDependentChild(target);
5108
5109 /* diffs for immutable hard disks are auto-reset by default */
5110 target->m->autoReset =
5111 that->base()->m->type == MediumType_Immutable ?
5112 TRUE : FALSE;
5113
5114 /* register with mVirtualBox as the last step and move to
5115 * Created state only on success (leaving an orphan file is
5116 * better than breaking media registry consistency) */
5117 rc = that->mVirtualBox->registerHardDisk(target);
5118
5119 if (FAILED(rc))
5120 {
5121 /* break the parent association on failure to register */
5122 target->mVirtualBox->addDependentChild(target);
5123 that->removeDependentChild(target);
5124 target->mParent.setNull();
5125 }
5126 }
5127
5128 thatLock.maybeEnter();
5129
5130 if (SUCCEEDED(rc))
5131 {
5132 target->m->state = MediumState_Created;
5133
5134 target->m->size = size;
5135 target->m->logicalSize = logicalSize;
5136 }
5137 else
5138 {
5139 /* back to NotCreated on failure */
5140 target->m->state = MediumState_NotCreated;
5141
5142 target->m->autoReset = FALSE;
5143
5144 /* reset UUID to prevent it from being reused next time */
5145 if (generateUuid)
5146 unconst(target->m->id).clear();
5147 }
5148
5149 if (isAsync)
5150 {
5151 /* unlock ourselves when done (unless in MediumState_LockedWrite
5152 * state because of taking the online snapshot*/
5153 if (that->m->state != MediumState_LockedWrite)
5154 {
5155 HRESULT rc2 = that->UnlockRead(NULL);
5156 AssertComRC(rc2);
5157 }
5158 }
5159
5160 /* deregister the task registered in createDiffStorage() */
5161 Assert(that->m->numCreateDiffTasks != 0);
5162 --that->m->numCreateDiffTasks;
5163
5164 /* Note that in sync mode, it's the caller's responsibility to
5165 * unlock the hard disk */
5166
5167 break;
5168 }
5169
5170 ////////////////////////////////////////////////////////////////////////
5171
5172 case Task::Merge:
5173 {
5174 /* The lock is also used as a signal from the task initiator (which
5175 * releases it only after RTThreadCreate()) that we can start the
5176 * job. We don't actually need the lock for anything else since the
5177 * object is protected by MediumState_Deleting and we don't modify
5178 * its sensitive fields below */
5179 {
5180 AutoWriteLock thatLock(that);
5181 }
5182
5183 MergeChain *chain = task->d.chain.get();
5184
5185#if 0
5186 LogFlow(("*** MERGE forward = %RTbool\n", chain->isForward()));
5187#endif
5188
5189 try
5190 {
5191 PVBOXHDD hdd;
5192 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5193 ComAssertRCThrow(vrc, E_FAIL);
5194
5195 try
5196 {
5197 /* Open all hard disks in the chain (they are in the
5198 * {parent,child} order in there. Note that we don't lock
5199 * objects in this chain since they must be in states
5200 * (Deleting and LockedWrite) that prevent from changing
5201 * their format and location fields from outside. */
5202
5203 for (MergeChain::const_iterator it = chain->begin();
5204 it != chain->end(); ++ it)
5205 {
5206 /* complex sanity (sane complexity) */
5207 Assert((chain->isForward() &&
5208 ((*it != chain->back() &&
5209 (*it)->m->state == MediumState_Deleting) ||
5210 (*it == chain->back() &&
5211 (*it)->m->state == MediumState_LockedWrite))) ||
5212 (!chain->isForward() &&
5213 ((*it != chain->front() &&
5214 (*it)->m->state == MediumState_Deleting) ||
5215 (*it == chain->front() &&
5216 (*it)->m->state == MediumState_LockedWrite))));
5217
5218 Assert(*it == chain->target() ||
5219 (*it)->m->backRefs.size() == 0);
5220
5221 /* open the first image with VDOPEN_FLAGS_INFO because
5222 * it's not necessarily the base one */
5223 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5224 Utf8Str((*it)->m->locationFull).c_str(),
5225 it == chain->begin() ?
5226 VD_OPEN_FLAGS_INFO : 0,
5227 (*it)->m->vdDiskIfaces);
5228 if (RT_FAILURE(vrc))
5229 throw vrc;
5230#if 0
5231 LogFlow(("*** MERGE disk = %ls\n", (*it)->m->locationFull.raw()));
5232#endif
5233 }
5234
5235 /* needed for vdProgressCallback */
5236 that->m->vdProgress = task->progress;
5237
5238 unsigned start = chain->isForward() ?
5239 0 : (unsigned)chain->size() - 1;
5240 unsigned end = chain->isForward() ?
5241 (unsigned)chain->size() - 1 : 0;
5242#if 0
5243 LogFlow(("*** MERGE from %d to %d\n", start, end));
5244#endif
5245 vrc = VDMerge(hdd, start, end, that->m->vdDiskIfaces);
5246
5247 that->m->vdProgress = NULL;
5248
5249 if (RT_FAILURE(vrc))
5250 throw vrc;
5251
5252 /* update parent UUIDs */
5253 /// @todo VDMerge should be taught to do so, including the
5254 /// multiple children case
5255 if (chain->isForward())
5256 {
5257 /* target's UUID needs to be updated (note that target
5258 * is the only image in the container on success) */
5259 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5260 if (RT_FAILURE(vrc))
5261 throw vrc;
5262 }
5263 else
5264 {
5265 /* we need to update UUIDs of all source's children
5266 * which cannot be part of the container at once so
5267 * add each one in there individually */
5268 if (chain->children().size() > 0)
5269 {
5270 for (List::const_iterator it = chain->children().begin();
5271 it != chain->children().end(); ++ it)
5272 {
5273 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5274 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5275 Utf8Str((*it)->m->locationFull).c_str(),
5276 VD_OPEN_FLAGS_INFO,
5277 (*it)->m->vdDiskIfaces);
5278 if (RT_FAILURE(vrc))
5279 throw vrc;
5280
5281 vrc = VDSetParentUuid(hdd, 1,
5282 chain->target()->m->id);
5283 if (RT_FAILURE(vrc))
5284 throw vrc;
5285
5286 vrc = VDClose(hdd, false /* fDelete */);
5287 if (RT_FAILURE(vrc))
5288 throw vrc;
5289 }
5290 }
5291 }
5292 }
5293 catch (HRESULT aRC) { rc = aRC; }
5294 catch (int aVRC)
5295 {
5296 throw setError(E_FAIL,
5297 tr("Could not merge the hard disk '%ls' to '%ls'%s"),
5298 chain->source()->m->locationFull.raw(),
5299 chain->target()->m->locationFull.raw(),
5300 that->vdError(aVRC).raw());
5301 }
5302
5303 VDDestroy(hdd);
5304 }
5305 catch (HRESULT aRC) { rc = aRC; }
5306
5307 HRESULT rc2;
5308
5309 bool saveSettingsFailed = false;
5310
5311 if (SUCCEEDED(rc))
5312 {
5313 /* all hard disks but the target were successfully deleted by
5314 * VDMerge; reparent the last one and uninitialize deleted */
5315
5316 /* we set mParent & children() (note that thatLock is released
5317 * here), but lock VirtualBox first to follow the rule */
5318 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5319 that->treeLock());
5320
5321 Medium *source = chain->source();
5322 Medium *target = chain->target();
5323
5324 if (chain->isForward())
5325 {
5326 /* first, unregister the target since it may become a base
5327 * hard disk which needs re-registration */
5328 rc2 = target->mVirtualBox->
5329 unregisterHardDisk(target, false /* aSaveSettings */);
5330 AssertComRC(rc2);
5331
5332 /* then, reparent it and disconnect the deleted branch at
5333 * both ends (chain->parent() is source's parent) */
5334 target->mParent->removeDependentChild(target);
5335 target->mParent = chain->parent();
5336 if (!target->mParent.isNull())
5337 {
5338 target->mParent->addDependentChild(target);
5339 target->mParent->removeDependentChild(source);
5340 source->mParent.setNull();
5341 }
5342 else
5343 {
5344 target->mVirtualBox->addDependentChild(target);
5345 target->mVirtualBox->removeDependentChild(source);
5346 }
5347
5348 /* then, register again */
5349 rc2 = target->mVirtualBox->
5350 registerHardDisk(target, false /* aSaveSettings */);
5351 AssertComRC(rc2);
5352 }
5353 else
5354 {
5355 Assert(target->children().size() == 1);
5356 Medium *targetChild = target->children().front();
5357
5358 /* disconnect the deleted branch at the elder end */
5359 target->removeDependentChild(targetChild);
5360 targetChild->mParent.setNull();
5361
5362 const List &children = chain->children();
5363
5364 /* reparent source's chidren and disconnect the deleted
5365 * branch at the younger end m*/
5366 if (children.size() > 0)
5367 {
5368 /* obey {parent,child} lock order */
5369 AutoWriteLock sourceLock(source);
5370
5371 for (List::const_iterator it = children.begin();
5372 it != children.end(); ++ it)
5373 {
5374 AutoWriteLock childLock(*it);
5375
5376 (*it)->mParent = target;
5377 (*it)->mParent->addDependentChild(*it);
5378 source->removeDependentChild(*it);
5379 }
5380 }
5381 }
5382
5383 /* try to save the hard disk registry */
5384 rc = that->mVirtualBox->saveSettings();
5385
5386 if (SUCCEEDED(rc))
5387 {
5388 /* unregister and uninitialize all hard disks in the chain
5389 * but the target */
5390
5391 for (MergeChain::iterator it = chain->begin();
5392 it != chain->end();)
5393 {
5394 if (*it == chain->target())
5395 {
5396 ++ it;
5397 continue;
5398 }
5399
5400 rc2 = (*it)->mVirtualBox->
5401 unregisterHardDisk(*it, false /* aSaveSettings */);
5402 AssertComRC(rc2);
5403
5404 /* now, uninitialize the deleted hard disk (note that
5405 * due to the Deleting state, uninit() will not touch
5406 * the parent-child relationship so we need to
5407 * uninitialize each disk individually) */
5408
5409 /* note that the operation initiator hard disk (which is
5410 * normally also the source hard disk) is a special case
5411 * -- there is one more caller added by Task to it which
5412 * we must release. Also, if we are in sync mode, the
5413 * caller may still hold an AutoCaller instance for it
5414 * and therefore we cannot uninit() it (it's therefore
5415 * the caller's responsibility) */
5416 if (*it == that)
5417 task->autoCaller.release();
5418
5419 /* release the caller added by MergeChain before
5420 * uninit() */
5421 (*it)->releaseCaller();
5422
5423 if (isAsync || *it != that)
5424 (*it)->uninit();
5425
5426 /* delete (to prevent uninitialization in MergeChain
5427 * dtor) and advance to the next item */
5428 it = chain->erase(it);
5429 }
5430
5431 /* Note that states of all other hard disks (target, parent,
5432 * children) will be restored by the MergeChain dtor */
5433 }
5434 else
5435 {
5436 /* too bad if we fail, but we'll need to rollback everything
5437 * we did above to at least keep the HD tree in sync with
5438 * the current registry on disk */
5439
5440 saveSettingsFailed = true;
5441
5442 /// @todo NEWMEDIA implement a proper undo
5443
5444 AssertFailed();
5445 }
5446 }
5447
5448 if (FAILED(rc))
5449 {
5450 /* Here we come if either VDMerge() failed (in which case we
5451 * assume that it tried to do everything to make a further
5452 * retry possible -- e.g. not deleted intermediate hard disks
5453 * and so on) or VirtualBox::saveSettings() failed (where we
5454 * should have the original tree but with intermediate storage
5455 * units deleted by VDMerge()). We have to only restore states
5456 * (through the MergeChain dtor) unless we are run synchronously
5457 * in which case it's the responsibility of the caller as stated
5458 * in the mergeTo() docs. The latter also implies that we
5459 * don't own the merge chain, so release it in this case. */
5460
5461 if (!isAsync)
5462 task->d.chain.release();
5463
5464 NOREF(saveSettingsFailed);
5465 }
5466
5467 break;
5468 }
5469
5470 ////////////////////////////////////////////////////////////////////////
5471
5472 case Task::Clone:
5473 {
5474 ComObjPtr<Medium> &target = task->d.target;
5475 ComObjPtr<Medium> &parent = task->d.parentDisk;
5476
5477 /* Lock all in {parent,child} order. The lock is also used as a
5478 * signal from the task initiator (which releases it only after
5479 * RTThreadCreate()) that we can start the job. */
5480 AutoMultiWriteLock3 thatLock(that, target, parent);
5481
5482 ImageChain *srcChain = task->d.source.get();
5483 ImageChain *parentChain = task->d.parent.get();
5484
5485 uint64_t size = 0, logicalSize = 0;
5486
5487 /* The object may request a specific UUID (through a special form of
5488 * the setLocation() argument). Otherwise we have to generate it */
5489 Guid targetId = target->m->id;
5490 bool generateUuid = targetId.isEmpty();
5491 if (generateUuid)
5492 {
5493 targetId.create();
5494 /* VirtualBox::registerHardDisk() will need UUID */
5495 unconst(target->m->id) = targetId;
5496 }
5497
5498 try
5499 {
5500 PVBOXHDD hdd;
5501 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5502 ComAssertRCThrow(vrc, E_FAIL);
5503
5504 try
5505 {
5506 /* Open all hard disk images in the source chain. */
5507 for (List::const_iterator it = srcChain->begin();
5508 it != srcChain->end(); ++ it)
5509 {
5510 /* sanity check */
5511 Assert((*it)->m->state == MediumState_LockedRead);
5512
5513 /** Open all images in read-only mode. */
5514 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5515 Utf8Str((*it)->m->locationFull).c_str(),
5516 VD_OPEN_FLAGS_READONLY,
5517 (*it)->m->vdDiskIfaces);
5518 if (RT_FAILURE(vrc))
5519 {
5520 throw setError(E_FAIL,
5521 tr("Could not open the hard disk storage unit '%s'%s"),
5522 Utf8Str((*it)->m->locationFull).raw(),
5523 that->vdError(vrc).raw());
5524 }
5525 }
5526
5527 /* unlock before the potentially lengthy operation */
5528 thatLock.leave();
5529
5530 Utf8Str targetFormat(target->m->format);
5531 Utf8Str targetLocation(target->m->locationFull);
5532
5533 Assert( target->m->state == MediumState_Creating
5534 || target->m->state == MediumState_LockedWrite);
5535 Assert(that->m->state == MediumState_LockedRead);
5536 Assert(parent.isNull() || parent->m->state == MediumState_LockedRead);
5537
5538 /* ensure the target directory exists */
5539 rc = VirtualBox::ensureFilePathExists(targetLocation);
5540 CheckComRCThrowRC(rc);
5541
5542 /* needed for vdProgressCallback */
5543 that->m->vdProgress = task->progress;
5544
5545 PVBOXHDD targetHdd;
5546 int vrc = VDCreate(that->m->vdDiskIfaces, &targetHdd);
5547 ComAssertRCThrow(vrc, E_FAIL);
5548
5549 try
5550 {
5551 /* Open all hard disk images in the parent chain. */
5552 for (List::const_iterator it = parentChain->begin();
5553 it != parentChain->end(); ++ it)
5554 {
5555 /* sanity check */
5556 Assert( (*it)->m->state == MediumState_LockedRead
5557 || (*it)->m->state == MediumState_LockedWrite);
5558
5559 /* Open all images in appropriate mode. */
5560 vrc = VDOpen(targetHdd, Utf8Str((*it)->m->format).c_str(),
5561 Utf8Str((*it)->m->locationFull).c_str(),
5562 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5563 (*it)->m->vdDiskIfaces);
5564 if (RT_FAILURE(vrc))
5565 {
5566 throw setError(E_FAIL,
5567 tr("Could not open the hard disk storage unit '%s'%s"),
5568 Utf8Str((*it)->m->locationFull).raw(),
5569 that->vdError(vrc).raw());
5570 }
5571 }
5572
5573 vrc = VDCopy(hdd, VD_LAST_IMAGE, targetHdd,
5574 targetFormat.c_str(),
5575 target->m->state == MediumState_Creating ? targetLocation.raw() : (char *)NULL,
5576 false, 0,
5577 task->d.variant, targetId.raw(), NULL,
5578 target->m->vdDiskIfaces,
5579 that->m->vdDiskIfaces);
5580
5581 that->m->vdProgress = NULL;
5582
5583 if (RT_FAILURE(vrc))
5584 {
5585 throw setError(E_FAIL,
5586 tr("Could not create the clone hard disk '%s'%s"),
5587 targetLocation.raw(), that->vdError(vrc).raw());
5588 }
5589 size = VDGetFileSize(targetHdd, 0);
5590 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5591 }
5592 catch (HRESULT aRC) { rc = aRC; }
5593
5594 VDDestroy(targetHdd);
5595 }
5596 catch (HRESULT aRC) { rc = aRC; }
5597
5598 VDDestroy(hdd);
5599 }
5600 catch (HRESULT aRC) { rc = aRC; }
5601
5602 /* Only do the parent changes for newly created images. */
5603 if (target->m->state == MediumState_Creating)
5604 {
5605 if (SUCCEEDED(rc))
5606 {
5607 /* we set mParent & children() (note that thatLock is released
5608 * here), but lock VirtualBox first to follow the rule */
5609 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5610 that->treeLock());
5611
5612 Assert(target->mParent.isNull());
5613
5614 if (parent)
5615 {
5616 /* associate the clone with the parent and deassociate
5617 * from VirtualBox */
5618 target->mParent = parent;
5619 parent->addDependentChild(target);
5620 target->mVirtualBox->removeDependentChild(target);
5621
5622 /* register with mVirtualBox as the last step and move to
5623 * Created state only on success (leaving an orphan file is
5624 * better than breaking media registry consistency) */
5625 rc = parent->mVirtualBox->registerHardDisk(target);
5626
5627 if (FAILED(rc))
5628 {
5629 /* break parent association on failure to register */
5630 target->mVirtualBox->addDependentChild(target);
5631 parent->removeDependentChild(target);
5632 target->mParent.setNull();
5633 }
5634 }
5635 else
5636 {
5637 /* just register */
5638 rc = that->mVirtualBox->registerHardDisk(target);
5639 }
5640 }
5641 }
5642
5643 thatLock.maybeEnter();
5644
5645 if (target->m->state == MediumState_Creating)
5646 {
5647 if (SUCCEEDED(rc))
5648 {
5649 target->m->state = MediumState_Created;
5650
5651 target->m->size = size;
5652 target->m->logicalSize = logicalSize;
5653 }
5654 else
5655 {
5656 /* back to NotCreated on failure */
5657 target->m->state = MediumState_NotCreated;
5658
5659 /* reset UUID to prevent it from being reused next time */
5660 if (generateUuid)
5661 unconst(target->m->id).clear();
5662 }
5663 }
5664
5665 /* Everything is explicitly unlocked when the task exits,
5666 * as the task destruction also destroys the source chain. */
5667
5668 /* Make sure the source chain is released early. It could happen
5669 * that we get a deadlock in Appliance::Import when Medium::Close
5670 * is called & the source chain is released at the same time. */
5671 task->d.source.reset();
5672 break;
5673 }
5674
5675 ////////////////////////////////////////////////////////////////////////
5676
5677 case Task::Delete:
5678 {
5679 /* The lock is also used as a signal from the task initiator (which
5680 * releases it only after RTThreadCreate()) that we can start the job */
5681 AutoWriteLock thatLock(that);
5682
5683 try
5684 {
5685 PVBOXHDD hdd;
5686 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5687 ComAssertRCThrow(vrc, E_FAIL);
5688
5689 Utf8Str format(that->m->format);
5690 Utf8Str location(that->m->locationFull);
5691
5692 /* unlock before the potentially lengthy operation */
5693 Assert(that->m->state == MediumState_Deleting);
5694 thatLock.leave();
5695
5696 try
5697 {
5698 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5699 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5700 that->m->vdDiskIfaces);
5701 if (RT_SUCCESS(vrc))
5702 vrc = VDClose(hdd, true /* fDelete */);
5703
5704 if (RT_FAILURE(vrc))
5705 {
5706 throw setError(E_FAIL,
5707 tr("Could not delete the hard disk storage unit '%s'%s"),
5708 location.raw(), that->vdError(vrc).raw());
5709 }
5710
5711 }
5712 catch (HRESULT aRC) { rc = aRC; }
5713
5714 VDDestroy(hdd);
5715 }
5716 catch (HRESULT aRC) { rc = aRC; }
5717
5718 thatLock.maybeEnter();
5719
5720 /* go to the NotCreated state even on failure since the storage
5721 * may have been already partially deleted and cannot be used any
5722 * more. One will be able to manually re-open the storage if really
5723 * needed to re-register it. */
5724 that->m->state = MediumState_NotCreated;
5725
5726 /* Reset UUID to prevent Create* from reusing it again */
5727 unconst(that->m->id).clear();
5728
5729 break;
5730 }
5731
5732 case Task::Reset:
5733 {
5734 /* The lock is also used as a signal from the task initiator (which
5735 * releases it only after RTThreadCreate()) that we can start the job */
5736 AutoWriteLock thatLock(that);
5737
5738 /// @todo Below we use a pair of delete/create operations to reset
5739 /// the diff contents but the most efficient way will of course be
5740 /// to add a VDResetDiff() API call
5741
5742 uint64_t size = 0, logicalSize = 0;
5743
5744 try
5745 {
5746 PVBOXHDD hdd;
5747 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5748 ComAssertRCThrow(vrc, E_FAIL);
5749
5750 Guid id = that->m->id;
5751 Utf8Str format(that->m->format);
5752 Utf8Str location(that->m->locationFull);
5753
5754 Guid parentId = that->mParent->m->id;
5755 Utf8Str parentFormat(that->mParent->m->format);
5756 Utf8Str parentLocation(that->mParent->m->locationFull);
5757
5758 Assert(that->m->state == MediumState_LockedWrite);
5759
5760 /* unlock before the potentially lengthy operation */
5761 thatLock.leave();
5762
5763 try
5764 {
5765 /* first, delete the storage unit */
5766 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5767 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5768 that->m->vdDiskIfaces);
5769 if (RT_SUCCESS(vrc))
5770 vrc = VDClose(hdd, true /* fDelete */);
5771
5772 if (RT_FAILURE(vrc))
5773 {
5774 throw setError(E_FAIL,
5775 tr("Could not delete the hard disk storage unit '%s'%s"),
5776 location.raw(), that->vdError(vrc).raw());
5777 }
5778
5779 /* next, create it again */
5780 vrc = VDOpen(hdd, parentFormat.c_str(), parentLocation.c_str(),
5781 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5782 that->m->vdDiskIfaces);
5783 if (RT_FAILURE(vrc))
5784 {
5785 throw setError(E_FAIL,
5786 tr("Could not open the hard disk storage unit '%s'%s"),
5787 parentLocation.raw(), that->vdError(vrc).raw());
5788 }
5789
5790 /* needed for vdProgressCallback */
5791 that->m->vdProgress = task->progress;
5792
5793 vrc = VDCreateDiff(hdd, format.c_str(), location.c_str(),
5794 /// @todo use the same image variant as before
5795 VD_IMAGE_FLAGS_NONE,
5796 NULL, id.raw(),
5797 parentId.raw(),
5798 VD_OPEN_FLAGS_NORMAL,
5799 that->m->vdDiskIfaces,
5800 that->m->vdDiskIfaces);
5801
5802 that->m->vdProgress = NULL;
5803
5804 if (RT_FAILURE(vrc))
5805 {
5806 throw setError(E_FAIL,
5807 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5808 location.raw(), that->vdError(vrc).raw());
5809 }
5810
5811 size = VDGetFileSize(hdd, 1);
5812 logicalSize = VDGetSize(hdd, 1) / _1M;
5813 }
5814 catch (HRESULT aRC) { rc = aRC; }
5815
5816 VDDestroy(hdd);
5817 }
5818 catch (HRESULT aRC) { rc = aRC; }
5819
5820 thatLock.enter();
5821
5822 that->m->size = size;
5823 that->m->logicalSize = logicalSize;
5824
5825 if (isAsync)
5826 {
5827 /* unlock ourselves when done */
5828 HRESULT rc2 = that->UnlockWrite(NULL);
5829 AssertComRC(rc2);
5830 }
5831
5832 /* Note that in sync mode, it's the caller's responsibility to
5833 * unlock the hard disk */
5834
5835 break;
5836 }
5837
5838 ////////////////////////////////////////////////////////////////////////
5839
5840 case Task::Compact:
5841 {
5842 /* Lock all in {parent,child} order. The lock is also used as a
5843 * signal from the task initiator (which releases it only after
5844 * RTThreadCreate()) that we can start the job. */
5845 AutoWriteLock thatLock(that);
5846
5847 ImageChain *imgChain = task->d.images.get();
5848
5849 try
5850 {
5851 PVBOXHDD hdd;
5852 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5853 ComAssertRCThrow(vrc, E_FAIL);
5854
5855 try
5856 {
5857 /* Open all hard disk images in the chain. */
5858 List::const_iterator last = imgChain->end();
5859 last--;
5860 for (List::const_iterator it = imgChain->begin();
5861 it != imgChain->end(); ++ it)
5862 {
5863 /* sanity check */
5864 if (it == last)
5865 Assert((*it)->m->state == MediumState_LockedWrite);
5866 else
5867 Assert((*it)->m->state == MediumState_LockedRead);
5868
5869 /** Open all images but last in read-only mode. */
5870 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5871 Utf8Str((*it)->m->locationFull).c_str(),
5872 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5873 (*it)->m->vdDiskIfaces);
5874 if (RT_FAILURE(vrc))
5875 {
5876 throw setError(E_FAIL,
5877 tr("Could not open the hard disk storage unit '%s'%s"),
5878 Utf8Str((*it)->m->locationFull).raw(),
5879 that->vdError(vrc).raw());
5880 }
5881 }
5882
5883 /* unlock before the potentially lengthy operation */
5884 thatLock.leave();
5885
5886 Assert(that->m->state == MediumState_LockedWrite);
5887
5888 /* needed for vdProgressCallback */
5889 that->m->vdProgress = task->progress;
5890
5891 vrc = VDCompact(hdd, VD_LAST_IMAGE, that->m->vdDiskIfaces);
5892
5893 that->m->vdProgress = NULL;
5894
5895 if (RT_FAILURE(vrc))
5896 {
5897 if (vrc == VERR_NOT_SUPPORTED)
5898 throw setError(VBOX_E_NOT_SUPPORTED,
5899 tr("Compacting is not supported yet for hard disk '%s'"),
5900 Utf8Str(that->m->locationFull).raw());
5901 else if (vrc == VERR_NOT_IMPLEMENTED)
5902 throw setError(E_NOTIMPL,
5903 tr("Compacting is not implemented, hard disk '%s'"),
5904 Utf8Str(that->m->locationFull).raw());
5905 else
5906 throw setError(E_FAIL,
5907 tr("Could not compact hard disk '%s'%s"),
5908 Utf8Str(that->m->locationFull).raw(),
5909 that->vdError(vrc).raw());
5910 }
5911 }
5912 catch (HRESULT aRC) { rc = aRC; }
5913
5914 VDDestroy(hdd);
5915 }
5916 catch (HRESULT aRC) { rc = aRC; }
5917
5918 /* Everything is explicitly unlocked when the task exits,
5919 * as the task destruction also destroys the image chain. */
5920
5921 break;
5922 }
5923
5924 default:
5925 AssertFailedReturn(VERR_GENERAL_FAILURE);
5926 }
5927
5928 /* complete the progress if run asynchronously */
5929 if (isAsync)
5930 {
5931 if (!task->progress.isNull())
5932 task->progress->notifyComplete(rc);
5933 }
5934 else
5935 {
5936 task->rc = rc;
5937 }
5938
5939 LogFlowFunc(("rc=%Rhrc\n", rc));
5940 LogFlowFuncLeave();
5941
5942 return VINF_SUCCESS;
5943
5944 /// @todo ugly hack, fix ComAssert... later
5945 #undef setError
5946}
5947
5948/* 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