VirtualBox

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

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

API/Medium: stub for resizing method

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