VirtualBox

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

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

Main/MediumImpl: fix closing of DVD/floppy media.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 189.4 KB
Line 
1/* $Id: MediumImpl.cpp 23591 2009-10-07 07:57:15Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008-2009 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "MediumImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27#include "VirtualBoxImpl.h"
28
29#include "Logging.h"
30
31#include <VBox/com/array.h>
32#include <VBox/com/SupportErrorInfo.h>
33
34#include <VBox/err.h>
35#include <VBox/settings.h>
36
37#include <iprt/param.h>
38#include <iprt/path.h>
39#include <iprt/file.h>
40#include <iprt/tcp.h>
41
42#include <VBox/VBoxHDD.h>
43
44#include <algorithm>
45
46////////////////////////////////////////////////////////////////////////////////
47//
48// Medium data definition
49//
50////////////////////////////////////////////////////////////////////////////////
51
52/** Describes how a machine refers to this image. */
53struct BackRef
54{
55 /** Equality predicate for stdc++. */
56 struct EqualsTo : public std::unary_function <BackRef, bool>
57 {
58 explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
59
60 bool operator()(const argument_type &aThat) const
61 {
62 return aThat.machineId == machineId;
63 }
64
65 const Guid machineId;
66 };
67
68 typedef std::list<Guid> GuidList;
69
70 BackRef() : 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(HostDrive)(BOOL *aHostDrive)
1437{
1438 CheckComArgOutPointerValid(aHostDrive);
1439
1440 AutoCaller autoCaller(this);
1441 CheckComRCReturnRC(autoCaller.rc());
1442
1443 AutoReadLock alock(this);
1444
1445 *aHostDrive = m->hostDrive;
1446
1447 return S_OK;
1448}
1449
1450STDMETHODIMP Medium::COMGETTER(Size)(ULONG64 *aSize)
1451{
1452 CheckComArgOutPointerValid(aSize);
1453
1454 AutoCaller autoCaller(this);
1455 CheckComRCReturnRC(autoCaller.rc());
1456
1457 AutoReadLock alock(this);
1458
1459 *aSize = m->size;
1460
1461 return S_OK;
1462}
1463
1464STDMETHODIMP Medium::COMGETTER(Format)(BSTR *aFormat)
1465{
1466 if (aFormat == NULL)
1467 return E_POINTER;
1468
1469 AutoCaller autoCaller(this);
1470 CheckComRCReturnRC(autoCaller.rc());
1471
1472 /* no need to lock, m->format is const */
1473 m->format.cloneTo(aFormat);
1474
1475 return S_OK;
1476}
1477
1478STDMETHODIMP Medium::COMGETTER(Type)(MediumType_T *aType)
1479{
1480 if (aType == NULL)
1481 return E_POINTER;
1482
1483 AutoCaller autoCaller(this);
1484 CheckComRCReturnRC(autoCaller.rc());
1485
1486 AutoReadLock alock(this);
1487
1488 *aType = m->type;
1489
1490 return S_OK;
1491}
1492
1493STDMETHODIMP Medium::COMSETTER(Type)(MediumType_T aType)
1494{
1495 AutoCaller autoCaller(this);
1496 CheckComRCReturnRC(autoCaller.rc());
1497
1498 /* VirtualBox::saveSettings() needs a write lock */
1499 AutoMultiWriteLock2 alock(mVirtualBox, this);
1500
1501 switch (m->state)
1502 {
1503 case MediumState_Created:
1504 case MediumState_Inaccessible:
1505 break;
1506 default:
1507 return setStateError();
1508 }
1509
1510 if (m->type == aType)
1511 {
1512 /* Nothing to do */
1513 return S_OK;
1514 }
1515
1516 /* we access mParent & children() */
1517 AutoReadLock treeLock(this->treeLock());
1518
1519 /* cannot change the type of a differencing hard disk */
1520 if (!mParent.isNull())
1521 return setError(E_FAIL,
1522 tr("Hard disk '%ls' is a differencing hard disk"),
1523 m->locationFull.raw());
1524
1525 /* cannot change the type of a hard disk being in use */
1526 if (m->backRefs.size() != 0)
1527 return setError(E_FAIL,
1528 tr("Hard disk '%ls' is attached to %d virtual machines"),
1529 m->locationFull.raw(), m->backRefs.size());
1530
1531 switch (aType)
1532 {
1533 case MediumType_Normal:
1534 case MediumType_Immutable:
1535 {
1536 /* normal can be easily converted to imutable and vice versa even
1537 * if they have children as long as they are not attached to any
1538 * machine themselves */
1539 break;
1540 }
1541 case MediumType_Writethrough:
1542 {
1543 /* cannot change to writethrough if there are children */
1544 if (children().size() != 0)
1545 return setError(E_FAIL,
1546 tr("Hard disk '%ls' has %d child hard disks"),
1547 children().size());
1548 break;
1549 }
1550 default:
1551 AssertFailedReturn(E_FAIL);
1552 }
1553
1554 m->type = aType;
1555
1556 HRESULT rc = mVirtualBox->saveSettings();
1557
1558 return rc;
1559}
1560
1561STDMETHODIMP Medium::COMGETTER(Parent)(IMedium **aParent)
1562{
1563 if (aParent == NULL)
1564 return E_POINTER;
1565
1566 AutoCaller autoCaller(this);
1567 CheckComRCReturnRC(autoCaller.rc());
1568
1569 /* we access mParent */
1570 AutoReadLock treeLock(this->treeLock());
1571
1572 mParent.queryInterfaceTo(aParent);
1573
1574 return S_OK;
1575}
1576
1577STDMETHODIMP Medium::COMGETTER(Children)(ComSafeArrayOut(IMedium *, aChildren))
1578{
1579 if (ComSafeArrayOutIsNull(aChildren))
1580 return E_POINTER;
1581
1582 AutoCaller autoCaller(this);
1583 CheckComRCReturnRC(autoCaller.rc());
1584
1585 /* we access children */
1586 AutoReadLock treeLock(this->treeLock());
1587
1588 SafeIfaceArray<IMedium> children(this->children());
1589 children.detachTo(ComSafeArrayOutArg(aChildren));
1590
1591 return S_OK;
1592}
1593
1594STDMETHODIMP Medium::COMGETTER(Base)(IMedium **aBase)
1595{
1596 if (aBase == NULL)
1597 return E_POINTER;
1598
1599 /* base() will do callers/locking */
1600
1601 base().queryInterfaceTo(aBase);
1602
1603 return S_OK;
1604}
1605
1606STDMETHODIMP Medium::COMGETTER(ReadOnly)(BOOL *aReadOnly)
1607{
1608 if (aReadOnly == NULL)
1609 return E_POINTER;
1610
1611 AutoCaller autoCaller(this);
1612 CheckComRCReturnRC(autoCaller.rc());
1613
1614 /* isRadOnly() will do locking */
1615
1616 *aReadOnly = isReadOnly();
1617
1618 return S_OK;
1619}
1620
1621STDMETHODIMP Medium::COMGETTER(LogicalSize)(ULONG64 *aLogicalSize)
1622{
1623 CheckComArgOutPointerValid(aLogicalSize);
1624
1625 {
1626 AutoCaller autoCaller(this);
1627 CheckComRCReturnRC(autoCaller.rc());
1628
1629 AutoReadLock alock(this);
1630
1631 /* we access mParent */
1632 AutoReadLock treeLock(this->treeLock());
1633
1634 if (mParent.isNull())
1635 {
1636 *aLogicalSize = m->logicalSize;
1637
1638 return S_OK;
1639 }
1640 }
1641
1642 /* We assume that some backend may decide to return a meaningless value in
1643 * response to VDGetSize() for differencing hard disks and therefore
1644 * always ask the base hard disk ourselves. */
1645
1646 /* base() will do callers/locking */
1647
1648 return base()->COMGETTER(LogicalSize)(aLogicalSize);
1649}
1650
1651STDMETHODIMP Medium::COMGETTER(AutoReset)(BOOL *aAutoReset)
1652{
1653 CheckComArgOutPointerValid(aAutoReset);
1654
1655 AutoCaller autoCaller(this);
1656 CheckComRCReturnRC(autoCaller.rc());
1657
1658 AutoReadLock alock(this);
1659
1660 if (mParent.isNull())
1661 *aAutoReset = FALSE;
1662
1663 *aAutoReset = m->autoReset;
1664
1665 return S_OK;
1666}
1667
1668STDMETHODIMP Medium::COMSETTER(AutoReset)(BOOL aAutoReset)
1669{
1670 AutoCaller autoCaller(this);
1671 CheckComRCReturnRC(autoCaller.rc());
1672
1673 /* VirtualBox::saveSettings() needs a write lock */
1674 AutoMultiWriteLock2 alock(mVirtualBox, this);
1675
1676 if (mParent.isNull())
1677 return setError(VBOX_E_NOT_SUPPORTED,
1678 tr("Hard disk '%ls' is not differencing"),
1679 m->locationFull.raw());
1680
1681 if (m->autoReset != aAutoReset)
1682 {
1683 m->autoReset = aAutoReset;
1684
1685 return mVirtualBox->saveSettings();
1686 }
1687
1688 return S_OK;
1689}
1690STDMETHODIMP Medium::COMGETTER(LastAccessError)(BSTR *aLastAccessError)
1691{
1692 CheckComArgOutPointerValid(aLastAccessError);
1693
1694 AutoCaller autoCaller(this);
1695 CheckComRCReturnRC(autoCaller.rc());
1696
1697 AutoReadLock alock(this);
1698
1699 if (m->lastAccessError.isEmpty())
1700 Bstr("").cloneTo(aLastAccessError);
1701 else
1702 m->lastAccessError.cloneTo(aLastAccessError);
1703
1704 return S_OK;
1705}
1706
1707STDMETHODIMP Medium::COMGETTER(MachineIds)(ComSafeArrayOut(BSTR,aMachineIds))
1708{
1709 if (ComSafeGUIDArrayOutIsNull(aMachineIds))
1710 return E_POINTER;
1711
1712 AutoCaller autoCaller(this);
1713 CheckComRCReturnRC(autoCaller.rc());
1714
1715 AutoReadLock alock(this);
1716
1717 com::SafeArray<BSTR> machineIds;
1718
1719 if (m->backRefs.size() != 0)
1720 {
1721 machineIds.reset(m->backRefs.size());
1722
1723 size_t i = 0;
1724 for (BackRefList::const_iterator it = m->backRefs.begin();
1725 it != m->backRefs.end(); ++ it, ++ i)
1726 {
1727 it->machineId.toUtf16().detachTo(&machineIds [i]);
1728 }
1729 }
1730
1731 machineIds.detachTo(ComSafeArrayOutArg(aMachineIds));
1732
1733 return S_OK;
1734}
1735
1736STDMETHODIMP Medium::GetSnapshotIds(IN_BSTR aMachineId,
1737 ComSafeArrayOut(BSTR, aSnapshotIds))
1738{
1739 CheckComArgExpr(aMachineId, Guid(aMachineId).isEmpty() == false);
1740 CheckComArgOutSafeArrayPointerValid(aSnapshotIds);
1741
1742 AutoCaller autoCaller(this);
1743 CheckComRCReturnRC(autoCaller.rc());
1744
1745 AutoReadLock alock(this);
1746
1747 com::SafeArray<BSTR> snapshotIds;
1748
1749 Guid id(aMachineId);
1750 for (BackRefList::const_iterator it = m->backRefs.begin();
1751 it != m->backRefs.end(); ++ it)
1752 {
1753 if (it->machineId == id)
1754 {
1755 size_t size = it->snapshotIds.size();
1756
1757 /* if the medium is attached to the machine in the current state, we
1758 * return its ID as the first element of the array */
1759 if (it->inCurState)
1760 ++ size;
1761
1762 if (size > 0)
1763 {
1764 snapshotIds.reset(size);
1765
1766 size_t j = 0;
1767 if (it->inCurState)
1768 it->machineId.toUtf16().detachTo(&snapshotIds [j ++]);
1769
1770 for (BackRef::GuidList::const_iterator jt =
1771 it->snapshotIds.begin();
1772 jt != it->snapshotIds.end(); ++ jt, ++ j)
1773 {
1774 (*jt).toUtf16().detachTo(&snapshotIds [j]);
1775 }
1776 }
1777
1778 break;
1779 }
1780 }
1781
1782 snapshotIds.detachTo(ComSafeArrayOutArg(aSnapshotIds));
1783
1784 return S_OK;
1785}
1786
1787/**
1788 * @note @a aState may be NULL if the state value is not needed (only for
1789 * in-process calls).
1790 */
1791STDMETHODIMP Medium::LockRead(MediumState_T *aState)
1792{
1793 AutoCaller autoCaller(this);
1794 CheckComRCReturnRC(autoCaller.rc());
1795
1796 AutoWriteLock alock(this);
1797
1798 /* return the current state before */
1799 if (aState)
1800 *aState = m->state;
1801
1802 HRESULT rc = S_OK;
1803
1804 switch (m->state)
1805 {
1806 case MediumState_Created:
1807 case MediumState_Inaccessible:
1808 case MediumState_LockedRead:
1809 {
1810 ++m->readers;
1811
1812 ComAssertMsgBreak(m->readers != 0, ("Counter overflow"), rc = E_FAIL);
1813
1814 if (m->state == MediumState_Created)
1815 m->accessibleInLock = true;
1816 else if (m->state == MediumState_Inaccessible)
1817 m->accessibleInLock = false;
1818
1819 m->state = MediumState_LockedRead;
1820
1821 break;
1822 }
1823 default:
1824 {
1825 rc = setStateError();
1826 break;
1827 }
1828 }
1829
1830 return rc;
1831}
1832
1833/**
1834 * @note @a aState may be NULL if the state value is not needed (only for
1835 * in-process calls).
1836 */
1837STDMETHODIMP Medium::UnlockRead(MediumState_T *aState)
1838{
1839 AutoCaller autoCaller(this);
1840 CheckComRCReturnRC(autoCaller.rc());
1841
1842 AutoWriteLock alock(this);
1843
1844 HRESULT rc = S_OK;
1845
1846 switch (m->state)
1847 {
1848 case MediumState_LockedRead:
1849 {
1850 if (m->queryInfoSem == NIL_RTSEMEVENTMULTI)
1851 {
1852 Assert(m->readers != 0);
1853 --m->readers;
1854
1855 /* Reset the state after the last reader */
1856 if (m->readers == 0)
1857 {
1858 if (m->accessibleInLock)
1859 m->state = MediumState_Created;
1860 else
1861 m->state = MediumState_Inaccessible;
1862 }
1863
1864 break;
1865 }
1866
1867 /* otherwise, queryInfo() is in progress; fall through */
1868 }
1869 default:
1870 {
1871 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1872 tr ("Medium '%ls' is not locked for reading"),
1873 m->locationFull.raw());
1874 break;
1875 }
1876 }
1877
1878 /* return the current state after */
1879 if (aState)
1880 *aState = m->state;
1881
1882 return rc;
1883}
1884
1885/**
1886 * @note @a aState may be NULL if the state value is not needed (only for
1887 * in-process calls).
1888 */
1889STDMETHODIMP Medium::LockWrite(MediumState_T *aState)
1890{
1891 AutoCaller autoCaller(this);
1892 CheckComRCReturnRC(autoCaller.rc());
1893
1894 AutoWriteLock alock(this);
1895
1896 /* return the current state before */
1897 if (aState)
1898 *aState = m->state;
1899
1900 HRESULT rc = S_OK;
1901
1902 switch (m->state)
1903 {
1904 case MediumState_Created:
1905 case MediumState_Inaccessible:
1906 {
1907 if (m->state == MediumState_Created)
1908 m->accessibleInLock = true;
1909 else if (m->state == MediumState_Inaccessible)
1910 m->accessibleInLock = false;
1911
1912 m->state = MediumState_LockedWrite;
1913 break;
1914 }
1915 default:
1916 {
1917 rc = setStateError();
1918 break;
1919 }
1920 }
1921
1922 return rc;
1923}
1924
1925/**
1926 * @note @a aState may be NULL if the state value is not needed (only for
1927 * in-process calls).
1928 */
1929STDMETHODIMP Medium::UnlockWrite(MediumState_T *aState)
1930{
1931 AutoCaller autoCaller(this);
1932 CheckComRCReturnRC(autoCaller.rc());
1933
1934 AutoWriteLock alock(this);
1935
1936 HRESULT rc = S_OK;
1937
1938 switch (m->state)
1939 {
1940 case MediumState_LockedWrite:
1941 {
1942 if (m->accessibleInLock)
1943 m->state = MediumState_Created;
1944 else
1945 m->state = MediumState_Inaccessible;
1946 break;
1947 }
1948 default:
1949 {
1950 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1951 tr ("Medium '%ls' is not locked for writing"),
1952 m->locationFull.raw());
1953 break;
1954 }
1955 }
1956
1957 /* return the current state after */
1958 if (aState)
1959 *aState = m->state;
1960
1961 return rc;
1962}
1963
1964STDMETHODIMP Medium::Close()
1965{
1966 AutoMayUninitSpan mayUninitSpan(this);
1967 CheckComRCReturnRC(mayUninitSpan.rc());
1968
1969 if (mayUninitSpan.alreadyInProgress())
1970 return S_OK;
1971
1972 /* unregisterWithVirtualBox() is assumed to always need a write mVirtualBox
1973 * lock as it is intenede to modify its internal structires. Also, we want
1974 * to unregister ourselves atomically after detecting that closure is
1975 * possible to make sure that we don't do that after another thread has done
1976 * VirtualBox::find*() but before it starts using us (provided that it holds
1977 * a mVirtualBox lock of course). */
1978
1979 AutoWriteLock vboxLock(mVirtualBox);
1980
1981 bool wasCreated = true;
1982
1983 switch (m->state)
1984 {
1985 case MediumState_NotCreated:
1986 wasCreated = false;
1987 break;
1988 case MediumState_Created:
1989 case MediumState_Inaccessible:
1990 break;
1991 default:
1992 return setStateError();
1993 }
1994
1995 if (m->backRefs.size() != 0)
1996 return setError(VBOX_E_OBJECT_IN_USE,
1997 tr("Medium '%ls' is attached to %d virtual machines"),
1998 m->locationFull.raw(), m->backRefs.size());
1999
2000 /* perform extra media-dependent close checks */
2001 HRESULT rc = canClose();
2002 CheckComRCReturnRC(rc);
2003
2004 if (wasCreated)
2005 {
2006 /* remove from the list of known media before performing actual
2007 * uninitialization (to keep the media registry consistent on
2008 * failure to do so) */
2009 rc = unregisterWithVirtualBox();
2010 CheckComRCReturnRC(rc);
2011 }
2012
2013 /* cause uninit() to happen on success */
2014 mayUninitSpan.acceptUninit();
2015
2016 return S_OK;
2017}
2018
2019STDMETHODIMP Medium::GetProperty(IN_BSTR aName, BSTR *aValue)
2020{
2021 CheckComArgStrNotEmptyOrNull(aName);
2022 CheckComArgOutPointerValid(aValue);
2023
2024 AutoCaller autoCaller(this);
2025 CheckComRCReturnRC(autoCaller.rc());
2026
2027 AutoReadLock alock(this);
2028
2029 Data::PropertyMap::const_iterator it = m->properties.find(Bstr(aName));
2030 if (it == m->properties.end())
2031 return setError(VBOX_E_OBJECT_NOT_FOUND,
2032 tr("Property '%ls' does not exist"), aName);
2033
2034 if (it->second.isEmpty())
2035 Bstr("").cloneTo(aValue);
2036 else
2037 it->second.cloneTo(aValue);
2038
2039 return S_OK;
2040}
2041
2042STDMETHODIMP Medium::SetProperty(IN_BSTR aName, IN_BSTR aValue)
2043{
2044 CheckComArgStrNotEmptyOrNull(aName);
2045
2046 AutoCaller autoCaller(this);
2047 CheckComRCReturnRC(autoCaller.rc());
2048
2049 /* VirtualBox::saveSettings() needs a write lock */
2050 AutoMultiWriteLock2 alock(mVirtualBox, this);
2051
2052 switch (m->state)
2053 {
2054 case MediumState_Created:
2055 case MediumState_Inaccessible:
2056 break;
2057 default:
2058 return setStateError();
2059 }
2060
2061 Data::PropertyMap::iterator it = m->properties.find(Bstr(aName));
2062 if (it == m->properties.end())
2063 return setError(VBOX_E_OBJECT_NOT_FOUND,
2064 tr("Property '%ls' does not exist"),
2065 aName);
2066
2067 if (aValue && !*aValue)
2068 it->second = (const char *)NULL;
2069 else
2070 it->second = aValue;
2071
2072 HRESULT rc = mVirtualBox->saveSettings();
2073
2074 return rc;
2075}
2076
2077STDMETHODIMP Medium::GetProperties(IN_BSTR aNames,
2078 ComSafeArrayOut(BSTR, aReturnNames),
2079 ComSafeArrayOut(BSTR, aReturnValues))
2080{
2081 CheckComArgOutSafeArrayPointerValid(aReturnNames);
2082 CheckComArgOutSafeArrayPointerValid(aReturnValues);
2083
2084 AutoCaller autoCaller(this);
2085 CheckComRCReturnRC(autoCaller.rc());
2086
2087 AutoReadLock alock(this);
2088
2089 /// @todo make use of aNames according to the documentation
2090 NOREF(aNames);
2091
2092 com::SafeArray<BSTR> names(m->properties.size());
2093 com::SafeArray<BSTR> values(m->properties.size());
2094 size_t i = 0;
2095
2096 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2097 it != m->properties.end();
2098 ++it)
2099 {
2100 it->first.cloneTo(&names[i]);
2101 if (it->second.isEmpty())
2102 Bstr("").cloneTo(&values [i]);
2103 else
2104 it->second.cloneTo(&values [i]);
2105 ++ i;
2106 }
2107
2108 names.detachTo(ComSafeArrayOutArg(aReturnNames));
2109 values.detachTo(ComSafeArrayOutArg(aReturnValues));
2110
2111 return S_OK;
2112}
2113
2114STDMETHODIMP Medium::SetProperties(ComSafeArrayIn(IN_BSTR, aNames),
2115 ComSafeArrayIn(IN_BSTR, aValues))
2116{
2117 CheckComArgSafeArrayNotNull(aNames);
2118 CheckComArgSafeArrayNotNull(aValues);
2119
2120 AutoCaller autoCaller(this);
2121 CheckComRCReturnRC(autoCaller.rc());
2122
2123 /* VirtualBox::saveSettings() needs a write lock */
2124 AutoMultiWriteLock2 alock(mVirtualBox, this);
2125
2126 com::SafeArray<IN_BSTR> names(ComSafeArrayInArg(aNames));
2127 com::SafeArray<IN_BSTR> values(ComSafeArrayInArg(aValues));
2128
2129 /* first pass: validate names */
2130 for (size_t i = 0;
2131 i < names.size();
2132 ++i)
2133 {
2134 if (m->properties.find(Bstr(names[i])) == m->properties.end())
2135 return setError(VBOX_E_OBJECT_NOT_FOUND,
2136 tr("Property '%ls' does not exist"), names[i]);
2137 }
2138
2139 /* second pass: assign */
2140 for (size_t i = 0;
2141 i < names.size();
2142 ++i)
2143 {
2144 Data::PropertyMap::iterator it = m->properties.find(Bstr(names[i]));
2145 AssertReturn(it != m->properties.end(), E_FAIL);
2146
2147 if (values[i] && !*values[i])
2148 it->second = (const char *)NULL;
2149 else
2150 it->second = values [i];
2151 }
2152
2153 HRESULT rc = mVirtualBox->saveSettings();
2154
2155 return rc;
2156}
2157
2158STDMETHODIMP Medium::CreateBaseStorage(ULONG64 aLogicalSize,
2159 MediumVariant_T aVariant,
2160 IProgress **aProgress)
2161{
2162 CheckComArgOutPointerValid(aProgress);
2163
2164 AutoCaller autoCaller(this);
2165 CheckComRCReturnRC(autoCaller.rc());
2166
2167 AutoWriteLock alock(this);
2168
2169 aVariant = (MediumVariant_T)((unsigned)aVariant & (unsigned)~MediumVariant_Diff);
2170 if ( !(aVariant & MediumVariant_Fixed)
2171 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2172 return setError(VBOX_E_NOT_SUPPORTED,
2173 tr("Hard disk format '%ls' does not support dynamic storage creation"),
2174 m->format.raw());
2175 if ( (aVariant & MediumVariant_Fixed)
2176 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2177 return setError(VBOX_E_NOT_SUPPORTED,
2178 tr("Hard disk format '%ls' does not support fixed storage creation"),
2179 m->format.raw());
2180
2181 switch (m->state)
2182 {
2183 case MediumState_NotCreated:
2184 break;
2185 default:
2186 return setStateError();
2187 }
2188
2189 ComObjPtr <Progress> progress;
2190 progress.createObject();
2191 /// @todo include fixed/dynamic
2192 HRESULT rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
2193 (aVariant & MediumVariant_Fixed)
2194 ? BstrFmt(tr("Creating fixed hard disk storage unit '%ls'"), m->locationFull.raw())
2195 : BstrFmt(tr("Creating dynamic hard disk storage unit '%ls'"), m->locationFull.raw()),
2196 TRUE /* aCancelable */);
2197 CheckComRCReturnRC(rc);
2198
2199 /* setup task object and thread to carry out the operation
2200 * asynchronously */
2201
2202 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateBase));
2203 AssertComRCReturnRC(task->autoCaller.rc());
2204
2205 task->d.size = aLogicalSize;
2206 task->d.variant = aVariant;
2207
2208 rc = task->startThread();
2209 CheckComRCReturnRC(rc);
2210
2211 /* go to Creating state on success */
2212 m->state = MediumState_Creating;
2213
2214 /* task is now owned by taskThread() so release it */
2215 task.release();
2216
2217 /* return progress to the caller */
2218 progress.queryInterfaceTo(aProgress);
2219
2220 return S_OK;
2221}
2222
2223STDMETHODIMP Medium::DeleteStorage(IProgress **aProgress)
2224{
2225 CheckComArgOutPointerValid(aProgress);
2226
2227 AutoCaller autoCaller(this);
2228 CheckComRCReturnRC(autoCaller.rc());
2229
2230 ComObjPtr <Progress> progress;
2231
2232 HRESULT rc = deleteStorageNoWait(progress);
2233 if (SUCCEEDED(rc))
2234 {
2235 /* return progress to the caller */
2236 progress.queryInterfaceTo(aProgress);
2237 }
2238
2239 return rc;
2240}
2241
2242STDMETHODIMP Medium::CreateDiffStorage(IMedium *aTarget,
2243 MediumVariant_T aVariant,
2244 IProgress **aProgress)
2245{
2246 CheckComArgNotNull(aTarget);
2247 CheckComArgOutPointerValid(aProgress);
2248
2249 AutoCaller autoCaller(this);
2250 CheckComRCReturnRC(autoCaller.rc());
2251
2252 ComObjPtr<Medium> diff;
2253 HRESULT rc = mVirtualBox->cast(aTarget, diff);
2254 CheckComRCReturnRC(rc);
2255
2256 AutoWriteLock alock(this);
2257
2258 if (m->type == MediumType_Writethrough)
2259 return setError(E_FAIL,
2260 tr("Hard disk '%ls' is Writethrough"),
2261 m->locationFull.raw());
2262
2263 /* We want to be locked for reading as long as our diff child is being
2264 * created */
2265 rc = LockRead(NULL);
2266 CheckComRCReturnRC(rc);
2267
2268 ComObjPtr <Progress> progress;
2269
2270 rc = createDiffStorageNoWait(diff, aVariant, progress);
2271 if (FAILED(rc))
2272 {
2273 HRESULT rc2 = UnlockRead(NULL);
2274 AssertComRC(rc2);
2275 /* Note: on success, taskThread() will unlock this */
2276 }
2277 else
2278 {
2279 /* return progress to the caller */
2280 progress.queryInterfaceTo(aProgress);
2281 }
2282
2283 return rc;
2284}
2285
2286STDMETHODIMP Medium::MergeTo(IN_BSTR /* aTargetId */, IProgress ** /* aProgress */)
2287{
2288 AutoCaller autoCaller(this);
2289 CheckComRCReturnRC(autoCaller.rc());
2290
2291 ReturnComNotImplemented();
2292}
2293
2294STDMETHODIMP Medium::CloneTo(IMedium *aTarget,
2295 MediumVariant_T aVariant,
2296 IMedium *aParent,
2297 IProgress **aProgress)
2298{
2299 CheckComArgNotNull(aTarget);
2300 CheckComArgOutPointerValid(aProgress);
2301
2302 AutoCaller autoCaller(this);
2303 CheckComRCReturnRC(autoCaller.rc());
2304
2305 ComObjPtr <Medium> target;
2306 HRESULT rc = mVirtualBox->cast(aTarget, target);
2307 CheckComRCReturnRC(rc);
2308 ComObjPtr <Medium> parent;
2309 if (aParent)
2310 {
2311 rc = mVirtualBox->cast(aParent, parent);
2312 CheckComRCReturnRC(rc);
2313 }
2314
2315 AutoMultiWriteLock3 alock(this, target, parent);
2316
2317 ComObjPtr <Progress> progress;
2318
2319 try
2320 {
2321 if ( target->m->state != MediumState_NotCreated
2322 && target->m->state != MediumState_Created)
2323 throw target->setStateError();
2324
2325 /** @todo separate out creating/locking an image chain from
2326 * SessionMachine::lockMedia and use it from here too.
2327 * logically this belongs into Medium functionality. */
2328
2329 /* Build the source chain and lock images in the proper order. */
2330 std::auto_ptr <ImageChain> srcChain(new ImageChain());
2331
2332 /* we walk the source tree */
2333 AutoReadLock srcTreeLock(this->treeLock());
2334 for (Medium *hd = this; hd; hd = hd->mParent)
2335 {
2336 rc = srcChain->addImage(hd);
2337 CheckComRCThrowRC(rc);
2338 }
2339 rc = srcChain->lockImagesRead();
2340 CheckComRCThrowRC(rc);
2341
2342 /* Build the parent chain and lock images in the proper order. */
2343 std::auto_ptr <ImageChain> parentChain(new ImageChain());
2344
2345 /* we walk the future parent tree */
2346 AutoReadLock parentTreeLock;
2347 if (parent)
2348 parentTreeLock.attach(parent->treeLock());
2349 for (Medium *hd = parent; hd; hd = hd->mParent)
2350 {
2351 rc = parentChain->addImage(hd);
2352 CheckComRCThrowRC(rc);
2353 }
2354 if (target->m->state == MediumState_Created)
2355 {
2356 /* If we're cloning to an existing image the parent chain also
2357 * contains the target image, and it gets locked for writing. */
2358 rc = parentChain->addImage(target);
2359 CheckComRCThrowRC(rc);
2360 rc = parentChain->lockImagesReadAndLastWrite();
2361 CheckComRCThrowRC(rc);
2362 }
2363 else
2364 {
2365 rc = parentChain->lockImagesRead();
2366 CheckComRCThrowRC(rc);
2367 }
2368
2369 progress.createObject();
2370 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2371 BstrFmt(tr("Creating clone hard disk '%ls'"),
2372 target->m->locationFull.raw()),
2373 TRUE /* aCancelable */);
2374 CheckComRCThrowRC(rc);
2375
2376 /* setup task object and thread to carry out the operation
2377 * asynchronously */
2378
2379 std::auto_ptr <Task> task(new Task(this, progress, Task::Clone));
2380 AssertComRCThrowRC(task->autoCaller.rc());
2381
2382 task->setData(target, parent);
2383 task->d.variant = aVariant;
2384 task->setData(srcChain.release(), parentChain.release());
2385
2386 rc = task->startThread();
2387 CheckComRCThrowRC(rc);
2388
2389 if (target->m->state == MediumState_NotCreated)
2390 {
2391 /* go to Creating state before leaving the lock */
2392 target->m->state = MediumState_Creating;
2393 }
2394
2395 /* task is now owned (or already deleted) by taskThread() so release it */
2396 task.release();
2397 }
2398 catch (HRESULT aRC)
2399 {
2400 rc = aRC;
2401 }
2402
2403 if (SUCCEEDED(rc))
2404 {
2405 /* return progress to the caller */
2406 progress.queryInterfaceTo(aProgress);
2407 }
2408
2409 return rc;
2410}
2411
2412STDMETHODIMP Medium::Compact(IProgress **aProgress)
2413{
2414 CheckComArgOutPointerValid(aProgress);
2415
2416 AutoCaller autoCaller(this);
2417 CheckComRCReturnRC(autoCaller.rc());
2418
2419 AutoWriteLock alock(this);
2420
2421 ComObjPtr <Progress> progress;
2422
2423 HRESULT rc = S_OK;
2424
2425 try
2426 {
2427 /** @todo separate out creating/locking an image chain from
2428 * SessionMachine::lockMedia and use it from here too.
2429 * logically this belongs into Medium functionality. */
2430
2431 /* Build the image chain and lock images in the proper order. */
2432 std::auto_ptr <ImageChain> imgChain(new ImageChain());
2433
2434 /* we walk the image tree */
2435 AutoReadLock srcTreeLock(this->treeLock());
2436 for (Medium *hd = this; hd; hd = hd->mParent)
2437 {
2438 rc = imgChain->addImage(hd);
2439 CheckComRCThrowRC(rc);
2440 }
2441 rc = imgChain->lockImagesReadAndLastWrite();
2442 CheckComRCThrowRC(rc);
2443
2444 progress.createObject();
2445 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2446 BstrFmt(tr("Compacting hard disk '%ls'"), m->locationFull.raw()),
2447 TRUE /* aCancelable */);
2448 CheckComRCThrowRC(rc);
2449
2450 /* setup task object and thread to carry out the operation
2451 * asynchronously */
2452
2453 std::auto_ptr <Task> task(new Task(this, progress, Task::Compact));
2454 AssertComRCThrowRC(task->autoCaller.rc());
2455
2456 task->setData(imgChain.release());
2457
2458 rc = task->startThread();
2459 CheckComRCThrowRC(rc);
2460
2461 /* task is now owned (or already deleted) by taskThread() so release it */
2462 task.release();
2463 }
2464 catch (HRESULT aRC)
2465 {
2466 rc = aRC;
2467 }
2468
2469 if (SUCCEEDED(rc))
2470 {
2471 /* return progress to the caller */
2472 progress.queryInterfaceTo(aProgress);
2473 }
2474
2475 return rc;
2476}
2477
2478STDMETHODIMP Medium::Reset(IProgress **aProgress)
2479{
2480 CheckComArgOutPointerValid(aProgress);
2481
2482 AutoCaller autoCaller(this);
2483 CheckComRCReturnRC(autoCaller.rc());
2484
2485 AutoWriteLock alock(this);
2486
2487 if (mParent.isNull())
2488 return setError(VBOX_E_NOT_SUPPORTED,
2489 tr ("Hard disk '%ls' is not differencing"),
2490 m->locationFull.raw());
2491
2492 HRESULT rc = canClose();
2493 CheckComRCReturnRC(rc);
2494
2495 rc = LockWrite(NULL);
2496 CheckComRCReturnRC(rc);
2497
2498 ComObjPtr <Progress> progress;
2499
2500 try
2501 {
2502 progress.createObject();
2503 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2504 BstrFmt(tr("Resetting differencing hard disk '%ls'"),
2505 m->locationFull.raw()),
2506 FALSE /* aCancelable */);
2507 CheckComRCThrowRC(rc);
2508
2509 /* setup task object and thread to carry out the operation
2510 * asynchronously */
2511
2512 std::auto_ptr <Task> task(new Task(this, progress, Task::Reset));
2513 AssertComRCThrowRC(task->autoCaller.rc());
2514
2515 rc = task->startThread();
2516 CheckComRCThrowRC(rc);
2517
2518 /* task is now owned (or already deleted) by taskThread() so release it */
2519 task.release();
2520 }
2521 catch (HRESULT aRC)
2522 {
2523 rc = aRC;
2524 }
2525
2526 if (FAILED (rc))
2527 {
2528 HRESULT rc2 = UnlockWrite(NULL);
2529 AssertComRC(rc2);
2530 /* Note: on success, taskThread() will unlock this */
2531 }
2532 else
2533 {
2534 /* return progress to the caller */
2535 progress.queryInterfaceTo(aProgress);
2536 }
2537
2538 return rc;
2539}
2540
2541////////////////////////////////////////////////////////////////////////////////
2542//
2543// Medium internal methods
2544//
2545////////////////////////////////////////////////////////////////////////////////
2546
2547/**
2548 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2549 * of this media and updates it if necessary to reflect the new location.
2550 *
2551 * @param aOldPath Old path (full).
2552 * @param aNewPath New path (full).
2553 *
2554 * @note Locks this object for writing.
2555 */
2556HRESULT Medium::updatePath(const char *aOldPath, const char *aNewPath)
2557{
2558 AssertReturn(aOldPath, E_FAIL);
2559 AssertReturn(aNewPath, E_FAIL);
2560
2561 AutoCaller autoCaller(this);
2562 CheckComRCReturnRC(autoCaller.rc());
2563
2564 AutoWriteLock alock(this);
2565
2566 LogFlowThisFunc(("locationFull.before='%s'\n", m->locationFull.raw()));
2567
2568 Utf8Str path = m->locationFull;
2569
2570 if (RTPathStartsWith(path.c_str(), aOldPath))
2571 {
2572 Utf8Str newPath = Utf8StrFmt("%s%s", aNewPath,
2573 path.raw() + strlen(aOldPath));
2574 path = newPath;
2575
2576 mVirtualBox->calculateRelativePath(path, path);
2577
2578 unconst(m->locationFull) = newPath;
2579 unconst(m->location) = path;
2580
2581 LogFlowThisFunc(("locationFull.after='%s'\n", m->locationFull.raw()));
2582 }
2583
2584 return S_OK;
2585}
2586
2587/**
2588 * Adds the given machine and optionally the snapshot to the list of the objects
2589 * this image is attached to.
2590 *
2591 * @param aMachineId Machine ID.
2592 * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
2593 */
2594HRESULT Medium::attachTo(const Guid &aMachineId,
2595 const Guid &aSnapshotId /*= Guid::Empty*/)
2596{
2597 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2598
2599 AutoCaller autoCaller(this);
2600 AssertComRCReturnRC(autoCaller.rc());
2601
2602 AutoWriteLock alock(this);
2603
2604 switch (m->state)
2605 {
2606 case MediumState_Created:
2607 case MediumState_Inaccessible:
2608 case MediumState_LockedRead:
2609 case MediumState_LockedWrite:
2610 break;
2611
2612 default:
2613 return setStateError();
2614 }
2615
2616 HRESULT rc = canAttach(aMachineId, aSnapshotId);
2617 CheckComRCReturnRC(rc);
2618
2619 BackRefList::iterator it =
2620 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2621 BackRef::EqualsTo(aMachineId));
2622 if (it == m->backRefs.end())
2623 {
2624 BackRef ref(aMachineId, aSnapshotId);
2625 m->backRefs.push_back(ref);
2626
2627 return S_OK;
2628 }
2629
2630 if (aSnapshotId.isEmpty())
2631 {
2632 /* sanity: no duplicate attachments */
2633 AssertReturn(!it->inCurState, E_FAIL);
2634 it->inCurState = true;
2635
2636 return S_OK;
2637 }
2638
2639 /* sanity: no duplicate attachments */
2640 BackRef::GuidList::const_iterator jt =
2641 std::find(it->snapshotIds.begin(), it->snapshotIds.end(), aSnapshotId);
2642 AssertReturn(jt == it->snapshotIds.end(), E_FAIL);
2643
2644 it->snapshotIds.push_back(aSnapshotId);
2645
2646 return S_OK;
2647}
2648
2649/**
2650 * Removes the given machine and optionally the snapshot from the list of the
2651 * objects this image is attached to.
2652 *
2653 * @param aMachineId Machine ID.
2654 * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
2655 * attachment.
2656 */
2657HRESULT Medium::detachFrom(const Guid &aMachineId,
2658 const Guid &aSnapshotId /*= Guid::Empty*/)
2659{
2660 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2661
2662 AutoCaller autoCaller(this);
2663 AssertComRCReturnRC(autoCaller.rc());
2664
2665 AutoWriteLock alock(this);
2666
2667 BackRefList::iterator it =
2668 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2669 BackRef::EqualsTo(aMachineId));
2670 AssertReturn(it != m->backRefs.end(), E_FAIL);
2671
2672 if (aSnapshotId.isEmpty())
2673 {
2674 /* remove the current state attachment */
2675 it->inCurState = false;
2676 }
2677 else
2678 {
2679 /* remove the snapshot attachment */
2680 BackRef::GuidList::iterator jt =
2681 std::find(it->snapshotIds.begin(), it->snapshotIds.end(), aSnapshotId);
2682
2683 AssertReturn(jt != it->snapshotIds.end(), E_FAIL);
2684 it->snapshotIds.erase(jt);
2685 }
2686
2687 /* if the backref becomes empty, remove it */
2688 if (it->inCurState == false && it->snapshotIds.size() == 0)
2689 m->backRefs.erase(it);
2690
2691 return S_OK;
2692}
2693
2694/**
2695 * Internal method to return the medium's GUID. Must have caller + locking!
2696 * @return
2697 */
2698const Guid& Medium::id() const
2699{
2700 return m->id;
2701}
2702
2703/**
2704 * Internal method to return the medium's GUID. Must have caller + locking!
2705 * @return
2706 */
2707MediumState_T Medium::state() const
2708{
2709 return m->state;
2710}
2711
2712/**
2713 * Internal method to return the medium's location. Must have caller + locking!
2714 * @return
2715 */
2716const Bstr& Medium::location() const
2717{
2718 return m->location;
2719}
2720
2721/**
2722 * Internal method to return the medium's full location. Must have caller + locking!
2723 * @return
2724 */
2725const Bstr& Medium::locationFull() const
2726{
2727 return m->locationFull;
2728}
2729
2730/**
2731 * Internal method to return the medium's list of backrefs. Must have caller + locking!
2732 * @return
2733 */
2734// const Medium::BackRefList& Medium::backRefs() const
2735// {
2736// return m->backRefs;
2737// }
2738
2739const Guid* Medium::getFirstMachineBackrefId() const
2740{
2741 if (!m->backRefs.size())
2742 return NULL;
2743
2744 return &m->backRefs.front().machineId;
2745}
2746
2747const Guid* Medium::getFirstMachineBackrefSnapshotId() const
2748{
2749 if (!m->backRefs.size())
2750 return NULL;
2751
2752 const BackRef &ref = m->backRefs.front();
2753 if (!ref.snapshotIds.size())
2754 return NULL;
2755
2756 return &ref.snapshotIds.front();
2757}
2758
2759/**
2760 * Internal method to check whether the medium is attached to the given machine. Must have caller + locking!
2761 * @return
2762 */
2763bool Medium::isAttachedTo(const Guid &aMachineId)
2764{
2765 BackRefList::iterator it =
2766 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2767 BackRef::EqualsTo(aMachineId));
2768 return it != m->backRefs.end() && it->inCurState;
2769}
2770
2771/**
2772 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2773 * of this hard disk or any its child and updates the paths if necessary to
2774 * reflect the new location.
2775 *
2776 * @param aOldPath Old path (full).
2777 * @param aNewPath New path (full).
2778 *
2779 * @note Locks treeLock() for reading, this object and all children for writing.
2780 */
2781void Medium::updatePaths(const char *aOldPath, const char *aNewPath)
2782{
2783 AssertReturnVoid(aOldPath);
2784 AssertReturnVoid(aNewPath);
2785
2786 AutoCaller autoCaller(this);
2787 AssertComRCReturnVoid(autoCaller.rc());
2788
2789 AutoWriteLock alock(this);
2790
2791 /* we access children() */
2792 AutoReadLock treeLock(this->treeLock());
2793
2794 updatePath(aOldPath, aNewPath);
2795
2796 /* update paths of all children */
2797 for (List::const_iterator it = children().begin();
2798 it != children().end();
2799 ++ it)
2800 {
2801 (*it)->updatePaths(aOldPath, aNewPath);
2802 }
2803}
2804
2805/**
2806 * Returns the base hard disk of the hard disk chain this hard disk is part of.
2807 *
2808 * The base hard disk is found by walking up the parent-child relationship axis.
2809 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
2810 * returns itself in response to this method.
2811 *
2812 * @param aLevel Where to store the number of ancestors of this hard disk
2813 * (zero for the base), may be @c NULL.
2814 *
2815 * @note Locks treeLock() for reading.
2816 */
2817ComObjPtr<Medium> Medium::base(uint32_t *aLevel /*= NULL*/)
2818{
2819 ComObjPtr <Medium> base;
2820 uint32_t level;
2821
2822 AutoCaller autoCaller(this);
2823 AssertReturn(autoCaller.isOk(), base);
2824
2825 /* we access mParent */
2826 AutoReadLock treeLock(this->treeLock());
2827
2828 base = this;
2829 level = 0;
2830
2831 if (!mParent.isNull())
2832 {
2833 for (;;)
2834 {
2835 AutoCaller baseCaller(base);
2836 AssertReturn(baseCaller.isOk(), base);
2837
2838 if (base->mParent.isNull())
2839 break;
2840
2841 base = base->mParent;
2842 ++ level;
2843 }
2844 }
2845
2846 if (aLevel != NULL)
2847 *aLevel = level;
2848
2849 return base;
2850}
2851
2852/**
2853 * Returns @c true if this hard disk cannot be modified because it has
2854 * dependants (children) or is part of the snapshot. Related to the hard disk
2855 * type and posterity, not to the current media state.
2856 *
2857 * @note Locks this object and treeLock() for reading.
2858 */
2859bool Medium::isReadOnly()
2860{
2861 AutoCaller autoCaller(this);
2862 AssertComRCReturn(autoCaller.rc(), false);
2863
2864 AutoReadLock alock(this);
2865
2866 /* we access children */
2867 AutoReadLock treeLock(this->treeLock());
2868
2869 switch (m->type)
2870 {
2871 case MediumType_Normal:
2872 {
2873 if (children().size() != 0)
2874 return true;
2875
2876 for (BackRefList::const_iterator it = m->backRefs.begin();
2877 it != m->backRefs.end(); ++ it)
2878 if (it->snapshotIds.size() != 0)
2879 return true;
2880
2881 return false;
2882 }
2883 case MediumType_Immutable:
2884 {
2885 return true;
2886 }
2887 case MediumType_Writethrough:
2888 {
2889 return false;
2890 }
2891 default:
2892 break;
2893 }
2894
2895 AssertFailedReturn(false);
2896}
2897
2898/**
2899 * Saves hard disk data by appending a new <HardDisk> child node to the given
2900 * parent node which can be either <HardDisks> or <HardDisk>.
2901 *
2902 * @param data Settings struct to be updated.
2903 *
2904 * @note Locks this object, treeLock() and children for reading.
2905 */
2906HRESULT Medium::saveSettings(settings::Medium &data)
2907{
2908 AutoCaller autoCaller(this);
2909 CheckComRCReturnRC(autoCaller.rc());
2910
2911 AutoReadLock alock(this);
2912
2913 /* we access mParent */
2914 AutoReadLock treeLock(this->treeLock());
2915
2916 data.uuid = m->id;
2917 data.strLocation = m->location;
2918 data.strFormat = m->format;
2919
2920 /* optional, only for diffs, default is false */
2921 if (!mParent.isNull())
2922 data.fAutoReset = !!m->autoReset;
2923 else
2924 data.fAutoReset = false;
2925
2926 /* optional */
2927 data.strDescription = m->description;
2928
2929 /* optional properties */
2930 data.properties.clear();
2931 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2932 it != m->properties.end();
2933 ++it)
2934 {
2935 /* only save properties that have non-default values */
2936 if (!it->second.isNull())
2937 {
2938 Utf8Str name = it->first;
2939 Utf8Str value = it->second;
2940 data.properties[name] = value;
2941 }
2942 }
2943
2944 /* only for base hard disks */
2945 if (mParent.isNull())
2946 data.hdType = m->type;
2947
2948 /* save all children */
2949 for (List::const_iterator it = children().begin();
2950 it != children().end();
2951 ++it)
2952 {
2953 settings::Medium m;
2954 HRESULT rc = (*it)->saveSettings(m);
2955 AssertComRCReturnRC(rc);
2956 data.llChildren.push_back(m);
2957 }
2958
2959 return S_OK;
2960}
2961
2962/**
2963 * Compares the location of this hard disk to the given location.
2964 *
2965 * The comparison takes the location details into account. For example, if the
2966 * location is a file in the host's filesystem, a case insensitive comparison
2967 * will be performed for case insensitive filesystems.
2968 *
2969 * @param aLocation Location to compare to (as is).
2970 * @param aResult Where to store the result of comparison: 0 if locations
2971 * are equal, 1 if this object's location is greater than
2972 * the specified location, and -1 otherwise.
2973 */
2974HRESULT Medium::compareLocationTo(const char *aLocation, int &aResult)
2975{
2976 AutoCaller autoCaller(this);
2977 AssertComRCReturnRC(autoCaller.rc());
2978
2979 AutoReadLock alock(this);
2980
2981 Utf8Str locationFull(m->locationFull);
2982
2983 /// @todo NEWMEDIA delegate the comparison to the backend?
2984
2985 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
2986 {
2987 Utf8Str location(aLocation);
2988
2989 /* For locations represented by files, append the default path if
2990 * only the name is given, and then get the full path. */
2991 if (!RTPathHavePath(aLocation))
2992 {
2993 location = Utf8StrFmt("%s%c%s",
2994 mVirtualBox->getDefaultHardDiskFolder().raw(),
2995 RTPATH_DELIMITER,
2996 aLocation);
2997 }
2998
2999 int vrc = mVirtualBox->calculateFullPath(location, location);
3000 if (RT_FAILURE(vrc))
3001 return setError(E_FAIL,
3002 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3003 location.raw(),
3004 vrc);
3005
3006 aResult = RTPathCompare(locationFull.c_str(), location.c_str());
3007 }
3008 else
3009 aResult = locationFull.compare(aLocation);
3010
3011 return S_OK;
3012}
3013
3014/**
3015 * Checks that this hard disk may be discarded and performs necessary state
3016 * changes.
3017 *
3018 * This method is to be called prior to calling the #discard() to perform
3019 * necessary consistency checks and place involved hard disks to appropriate
3020 * states. If #discard() is not called or fails, the state modifications
3021 * performed by this method must be undone by #cancelDiscard().
3022 *
3023 * See #discard() for more info about discarding hard disks.
3024 *
3025 * @param aChain Where to store the created merge chain (may return NULL
3026 * if no real merge is necessary).
3027 *
3028 * @note Locks treeLock() for reading. Locks this object, aTarget and all
3029 * intermediate hard disks for writing.
3030 */
3031HRESULT Medium::prepareDiscard(MergeChain * &aChain)
3032{
3033 AutoCaller autoCaller(this);
3034 AssertComRCReturnRC(autoCaller.rc());
3035
3036 aChain = NULL;
3037
3038 AutoWriteLock alock(this);
3039
3040 /* we access mParent & children() */
3041 AutoReadLock treeLock(this->treeLock());
3042
3043 AssertReturn(m->type == MediumType_Normal, E_FAIL);
3044
3045 if (children().size() == 0)
3046 {
3047 /* special treatment of the last hard disk in the chain: */
3048
3049 if (mParent.isNull())
3050 {
3051 /* lock only, to prevent any usage; discard() will unlock */
3052 return LockWrite(NULL);
3053 }
3054
3055 /* the differencing hard disk w/o children will be deleted, protect it
3056 * from attaching to other VMs (this is why Deleting) */
3057
3058 switch (m->state)
3059 {
3060 case MediumState_Created:
3061 m->state = MediumState_Deleting;
3062 break;
3063 default:
3064 return setStateError();
3065 }
3066
3067 /* aChain is intentionally NULL here */
3068
3069 return S_OK;
3070 }
3071
3072 /* not going multi-merge as it's too expensive */
3073 if (children().size() > 1)
3074 return setError(E_FAIL,
3075 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
3076 m->locationFull.raw(), children().size());
3077
3078 /* this is a read-only hard disk with children; it must be associated with
3079 * exactly one snapshot (when the snapshot is being taken, none of the
3080 * current VM's hard disks may be attached to other VMs). Note that by the
3081 * time when discard() is called, there must be no any attachments at all
3082 * (the code calling prepareDiscard() should detach). */
3083 AssertReturn(m->backRefs.size() == 1 &&
3084 !m->backRefs.front().inCurState &&
3085 m->backRefs.front().snapshotIds.size() == 1, E_FAIL);
3086
3087 ComObjPtr<Medium> child = children().front();
3088
3089 /* we keep this locked, so lock the affected child to make sure the lock
3090 * order is correct when calling prepareMergeTo() */
3091 AutoWriteLock childLock(child);
3092
3093 /* delegate the rest to the profi */
3094 if (mParent.isNull())
3095 {
3096 /* base hard disk, backward merge */
3097
3098 Assert(child->m->backRefs.size() == 1);
3099 if (child->m->backRefs.front().machineId != m->backRefs.front().machineId)
3100 {
3101 /* backward merge is too tricky, we'll just detach on discard, so
3102 * lock only, to prevent any usage; discard() will only unlock
3103 * (since we return NULL in aChain) */
3104 return LockWrite(NULL);
3105 }
3106
3107 return child->prepareMergeTo(this, aChain, true /* aIgnoreAttachments */);
3108 }
3109 else
3110 {
3111 /* forward merge */
3112 return prepareMergeTo(child, aChain, true /* aIgnoreAttachments */);
3113 }
3114}
3115
3116/**
3117 * Discards this hard disk.
3118 *
3119 * Discarding the hard disk is merging its contents to its differencing child
3120 * hard disk (forward merge) or contents of its child hard disk to itself
3121 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
3122 * a differencing hard disk w/o children, then it will be simply deleted.
3123 * Calling this method on a base hard disk w/o children will do nothing and
3124 * silently succeed. If this hard disk has more than one child, the method will
3125 * currently return an error (since merging in this case would be too expensive
3126 * and result in data duplication).
3127 *
3128 * When the backward merge takes place (i.e. this hard disk is a target) then,
3129 * on success, this hard disk will automatically replace the differencing child
3130 * hard disk used as a source (which will then be deleted) in the attachment
3131 * this child hard disk is associated with. This will happen only if both hard
3132 * disks belong to the same machine because otherwise such a replace would be
3133 * too tricky and could be not expected by the other machine. Same relates to a
3134 * case when the child hard disk is not associated with any machine at all. When
3135 * the backward merge is not applied, the method behaves as if the base hard
3136 * disk were not attached at all -- i.e. simply detaches it from the machine but
3137 * leaves the hard disk chain intact.
3138 *
3139 * This method is basically a wrapper around #mergeTo() that selects the correct
3140 * merge direction and performs additional actions as described above and.
3141 *
3142 * Note that this method will not return until the merge operation is complete
3143 * (which may be quite time consuming depending on the size of the merged hard
3144 * disks).
3145 *
3146 * Note that #prepareDiscard() must be called before calling this method. If
3147 * this method returns a failure, the caller must call #cancelDiscard(). On
3148 * success, #cancelDiscard() must not be called (this method will perform all
3149 * necessary steps such as resetting states of all involved hard disks and
3150 * deleting @a aChain).
3151 *
3152 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3153 * no real merge takes place).
3154 *
3155 * @note Locks the hard disks from the chain for writing. Locks the machine
3156 * object when the backward merge takes place. Locks treeLock() lock for
3157 * reading or writing.
3158 */
3159HRESULT Medium::discard(ComObjPtr<Progress> &aProgress, MergeChain *aChain)
3160{
3161 AssertReturn(!aProgress.isNull(), E_FAIL);
3162
3163 ComObjPtr <Medium> hdFrom;
3164
3165 HRESULT rc = S_OK;
3166
3167 {
3168 AutoCaller autoCaller(this);
3169 AssertComRCReturnRC(autoCaller.rc());
3170
3171 aProgress->SetNextOperation(BstrFmt(tr("Discarding hard disk '%s'"), name().raw()),
3172 1); // weight
3173
3174 if (aChain == NULL)
3175 {
3176 AutoWriteLock alock(this);
3177
3178 /* we access mParent & children() */
3179 AutoReadLock treeLock(this->treeLock());
3180
3181 Assert(children().size() == 0);
3182
3183 /* special treatment of the last hard disk in the chain: */
3184
3185 if (mParent.isNull())
3186 {
3187 rc = UnlockWrite(NULL);
3188 AssertComRC(rc);
3189 return rc;
3190 }
3191
3192 /* delete the differencing hard disk w/o children */
3193
3194 Assert(m->state == MediumState_Deleting);
3195
3196 /* go back to Created since deleteStorage() expects this state */
3197 m->state = MediumState_Created;
3198
3199 hdFrom = this;
3200
3201 rc = deleteStorageAndWait(&aProgress);
3202 }
3203 else
3204 {
3205 hdFrom = aChain->source();
3206
3207 rc = hdFrom->mergeToAndWait(aChain, &aProgress);
3208 }
3209 }
3210
3211 if (SUCCEEDED(rc))
3212 {
3213 /* mergeToAndWait() cannot uninitialize the initiator because of
3214 * possible AutoCallers on the current thread, deleteStorageAndWait()
3215 * doesn't do it either; do it ourselves */
3216 hdFrom->uninit();
3217 }
3218
3219 return rc;
3220}
3221
3222/**
3223 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
3224 * or fails. Frees memory occupied by @a aChain.
3225 *
3226 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3227 * no real merge takes place).
3228 *
3229 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
3230 * reading.
3231 */
3232void Medium::cancelDiscard(MergeChain *aChain)
3233{
3234 AutoCaller autoCaller(this);
3235 AssertComRCReturnVoid(autoCaller.rc());
3236
3237 if (aChain == NULL)
3238 {
3239 AutoWriteLock alock(this);
3240
3241 /* we access mParent & children() */
3242 AutoReadLock treeLock(this->treeLock());
3243
3244 Assert(children().size() == 0);
3245
3246 /* special treatment of the last hard disk in the chain: */
3247
3248 if (mParent.isNull())
3249 {
3250 HRESULT rc = UnlockWrite(NULL);
3251 AssertComRC(rc);
3252 return;
3253 }
3254
3255 /* the differencing hard disk w/o children will be deleted, protect it
3256 * from attaching to other VMs (this is why Deleting) */
3257
3258 Assert(m->state == MediumState_Deleting);
3259 m->state = MediumState_Created;
3260
3261 return;
3262 }
3263
3264 /* delegate the rest to the profi */
3265 cancelMergeTo(aChain);
3266}
3267
3268/**
3269 * Returns a preferred format for differencing hard disks.
3270 */
3271Bstr Medium::preferredDiffFormat()
3272{
3273 Bstr format;
3274
3275 AutoCaller autoCaller(this);
3276 AssertComRCReturn(autoCaller.rc(), format);
3277
3278 /* m->format is const, no need to lock */
3279 format = m->format;
3280
3281 /* check that our own format supports diffs */
3282 if (!(m->formatObj->capabilities() & MediumFormatCapabilities_Differencing))
3283 {
3284 /* use the default format if not */
3285 AutoReadLock propsLock(mVirtualBox->systemProperties());
3286 format = mVirtualBox->getDefaultHardDiskFormat();
3287 }
3288
3289 return format;
3290}
3291
3292/**
3293 * Returns the medium type. Must have caller + locking!
3294 * @return
3295 */
3296MediumType_T Medium::type() const
3297{
3298 return m->type;
3299}
3300
3301/**
3302 * Returns VirtualBox::hardDiskTreeHandle(), for convenience. Don't forget
3303 * to follow these locking rules:
3304 *
3305 * 1. The write lock on this handle must be either held alone on the thread
3306 * or requested *after* the VirtualBox object lock. Mixing with other
3307 * locks is prohibited.
3308 *
3309 * 2. The read lock on this handle may be intermixed with any other lock
3310 * with the exception that it must be requested *after* the VirtualBox
3311 * object lock.
3312 */
3313RWLockHandle* Medium::treeLock()
3314{
3315 return &mVirtualBox->hardDiskTreeLockHandle();
3316}
3317
3318// protected methods
3319////////////////////////////////////////////////////////////////////////////////
3320
3321/**
3322 * Returns a short version of the location attribute.
3323 *
3324 * @note Must be called from under this object's read or write lock.
3325 */
3326Utf8Str Medium::name()
3327{
3328 Utf8Str location(m->locationFull);
3329
3330 Utf8Str name = RTPathFilename(location.c_str());
3331 return name;
3332}
3333
3334/**
3335 * Sets the value of m->location and calculates the value of m->locationFull.
3336 *
3337 * Treats non-FS-path locations specially, and prepends the default hard disk
3338 * folder if the given location string does not contain any path information
3339 * at all.
3340 *
3341 * Also, if the specified location is a file path that ends with '/' then the
3342 * file name part will be generated by this method automatically in the format
3343 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
3344 * and assign to this medium, and <ext> is the default extension for this
3345 * medium's storage format. Note that this procedure requires the media state to
3346 * be NotCreated and will return a faiulre otherwise.
3347 *
3348 * @param aLocation Location of the storage unit. If the location is a FS-path,
3349 * then it can be relative to the VirtualBox home directory.
3350 * @param aFormat Optional fallback format if it is an import and the format
3351 * cannot be determined.
3352 *
3353 * @note Must be called from under this object's write lock.
3354 */
3355HRESULT Medium::setLocation(const Utf8Str &aLocation, const Utf8Str &aFormat)
3356{
3357 AssertReturn(!aLocation.isEmpty(), E_FAIL);
3358
3359 AutoCaller autoCaller(this);
3360 AssertComRCReturnRC(autoCaller.rc());
3361
3362 /* formatObj may be null only when initializing from an existing path and
3363 * no format is known yet */
3364 AssertReturn((!m->format.isNull() && !m->formatObj.isNull()) ||
3365 (autoCaller.state() == InInit &&
3366 m->state != MediumState_NotCreated && m->id.isEmpty() &&
3367 m->format.isNull() && m->formatObj.isNull()),
3368 E_FAIL);
3369
3370 /* are we dealing with a new hard disk constructed using the existing
3371 * location? */
3372 bool isImport = m->format.isNull();
3373
3374 if ( isImport
3375 || ( (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3376 && !m->hostDrive))
3377 {
3378 Guid id;
3379
3380 Utf8Str location(aLocation);
3381
3382 if (m->state == MediumState_NotCreated)
3383 {
3384 /* must be a file (formatObj must be already known) */
3385 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3386
3387 if (RTPathFilename(location.c_str()) == NULL)
3388 {
3389 /* no file name is given (either an empty string or ends with a
3390 * slash), generate a new UUID + file name if the state allows
3391 * this */
3392
3393 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3394 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3395 E_FAIL);
3396
3397 Bstr ext = m->formatObj->fileExtensions().front();
3398 ComAssertMsgRet(!ext.isEmpty(),
3399 ("Default extension must not be empty\n"),
3400 E_FAIL);
3401
3402 id.create();
3403
3404 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3405 location.raw(), id.raw(), ext.raw());
3406 }
3407 }
3408
3409 /* append the default folder if no path is given */
3410 if (!RTPathHavePath(location.c_str()))
3411 location = Utf8StrFmt("%s%c%s",
3412 mVirtualBox->getDefaultHardDiskFolder().raw(),
3413 RTPATH_DELIMITER,
3414 location.raw());
3415
3416 /* get the full file name */
3417 Utf8Str locationFull;
3418 int vrc = mVirtualBox->calculateFullPath(location, locationFull);
3419 if (RT_FAILURE(vrc))
3420 return setError(VBOX_E_FILE_ERROR,
3421 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3422 location.raw(), vrc);
3423
3424 /* detect the backend from the storage unit if importing */
3425 if (isImport)
3426 {
3427 char *backendName = NULL;
3428
3429 /* is it a file? */
3430 {
3431 RTFILE file;
3432 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ);
3433 if (RT_SUCCESS(vrc))
3434 RTFileClose(file);
3435 }
3436 if (RT_SUCCESS(vrc))
3437 {
3438 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3439 }
3440 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3441 {
3442 /* assume it's not a file, restore the original location */
3443 location = locationFull = aLocation;
3444 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3445 }
3446
3447 if (RT_FAILURE(vrc))
3448 {
3449 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3450 return setError(VBOX_E_FILE_ERROR,
3451 tr("Could not find file for the hard disk '%s' (%Rrc)"),
3452 locationFull.raw(), vrc);
3453 else if (aFormat.isEmpty())
3454 return setError(VBOX_E_IPRT_ERROR,
3455 tr("Could not get the storage format of the hard disk '%s' (%Rrc)"),
3456 locationFull.raw(), vrc);
3457 else
3458 {
3459 HRESULT rc = setFormat(Bstr(aFormat));
3460 /* setFormat() must not fail since we've just used the backend so
3461 * the format object must be there */
3462 AssertComRCReturnRC(rc);
3463 }
3464 }
3465 else
3466 {
3467 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3468
3469 HRESULT rc = setFormat(Bstr(backendName));
3470 RTStrFree(backendName);
3471
3472 /* setFormat() must not fail since we've just used the backend so
3473 * the format object must be there */
3474 AssertComRCReturnRC(rc);
3475 }
3476 }
3477
3478 /* is it still a file? */
3479 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3480 {
3481 m->location = location;
3482 m->locationFull = locationFull;
3483
3484 if (m->state == MediumState_NotCreated)
3485 {
3486 /* assign a new UUID (this UUID will be used when calling
3487 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3488 * also do that if we didn't generate it to make sure it is
3489 * either generated by us or reset to null */
3490 unconst(m->id) = id;
3491 }
3492 }
3493 else
3494 {
3495 m->location = locationFull;
3496 m->locationFull = locationFull;
3497 }
3498 }
3499 else
3500 {
3501 m->location = aLocation;
3502 m->locationFull = aLocation;
3503 }
3504
3505 return S_OK;
3506}
3507
3508/**
3509 * Queries information from the image file.
3510 *
3511 * As a result of this call, the accessibility state and data members such as
3512 * size and description will be updated with the current information.
3513 *
3514 * @note This method may block during a system I/O call that checks storage
3515 * accessibility.
3516 *
3517 * @note Locks treeLock() for reading and writing (for new diff media checked
3518 * for the first time). Locks mParent for reading. Locks this object for
3519 * writing.
3520 */
3521HRESULT Medium::queryInfo()
3522{
3523 AutoWriteLock alock(this);
3524
3525 AssertReturn(m->state == MediumState_Created ||
3526 m->state == MediumState_Inaccessible ||
3527 m->state == MediumState_LockedRead ||
3528 m->state == MediumState_LockedWrite,
3529 E_FAIL);
3530
3531 HRESULT rc = S_OK;
3532
3533 int vrc = VINF_SUCCESS;
3534
3535 /* check if a blocking queryInfo() call is in progress on some other thread,
3536 * and wait for it to finish if so instead of querying data ourselves */
3537 if (m->queryInfoSem != NIL_RTSEMEVENTMULTI)
3538 {
3539 Assert(m->state == MediumState_LockedRead);
3540
3541 ++m->queryInfoCallers;
3542 alock.leave();
3543
3544 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3545
3546 alock.enter();
3547 --m->queryInfoCallers;
3548
3549 if (m->queryInfoCallers == 0)
3550 {
3551 /* last waiting caller deletes the semaphore */
3552 RTSemEventMultiDestroy(m->queryInfoSem);
3553 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3554 }
3555
3556 AssertRC(vrc);
3557
3558 return S_OK;
3559 }
3560
3561 /* lazily create a semaphore for possible callers */
3562 vrc = RTSemEventMultiCreate(&m->queryInfoSem);
3563 ComAssertRCRet(vrc, E_FAIL);
3564
3565 bool tempStateSet = false;
3566 if (m->state != MediumState_LockedRead &&
3567 m->state != MediumState_LockedWrite)
3568 {
3569 /* Cause other methods to prevent any modifications before leaving the
3570 * lock. Note that clients will never see this temporary state change
3571 * since any COMGETTER(State) is (or will be) blocked until we finish
3572 * and restore the actual state. */
3573 m->state = MediumState_LockedRead;
3574 tempStateSet = true;
3575 }
3576
3577 /* leave the lock before a blocking operation */
3578 alock.leave();
3579
3580 bool success = false;
3581 Utf8Str lastAccessError;
3582
3583 try
3584 {
3585 Utf8Str location(m->locationFull);
3586
3587 /* totally useless to do accessibility checks for host drives */
3588 if (m->hostDrive)
3589 {
3590 success = true;
3591 throw S_OK;
3592 }
3593
3594 /* are we dealing with a new hard disk constructed using the existing
3595 * location? */
3596 bool isImport = m->id.isEmpty();
3597
3598 PVBOXHDD hdd;
3599 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3600 ComAssertRCThrow(vrc, E_FAIL);
3601
3602 try
3603 {
3604 unsigned flags = VD_OPEN_FLAGS_INFO;
3605
3606 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3607 * hard disks because that would prevent necessary modifications
3608 * when opening hard disks of some third-party formats for the first
3609 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3610 * generate an UUID if it is missing) */
3611 if ( (m->hddOpenMode == OpenReadOnly)
3612 || !isImport
3613 )
3614 flags |= VD_OPEN_FLAGS_READONLY;
3615
3616 /** @todo This kind of opening of images is assuming that diff
3617 * images can be opened as base images. Should be fixed ASAP. */
3618 vrc = VDOpen(hdd,
3619 Utf8Str(m->format).c_str(),
3620 location.c_str(),
3621 flags,
3622 m->vdDiskIfaces);
3623 if (RT_FAILURE(vrc))
3624 {
3625 lastAccessError = Utf8StrFmt(tr("Could not open the hard disk '%ls'%s"),
3626 m->locationFull.raw(), vdError(vrc).raw());
3627 throw S_OK;
3628 }
3629
3630 if (m->formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3631 {
3632 /* modify the UUIDs if necessary */
3633 if (m->setImageId)
3634 {
3635 vrc = VDSetUuid(hdd, 0, m->imageId);
3636 ComAssertRCThrow(vrc, E_FAIL);
3637 }
3638 if (m->setParentId)
3639 {
3640 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3641 ComAssertRCThrow(vrc, E_FAIL);
3642 }
3643 /* zap the information, these are no long-term members */
3644 m->setImageId = false;
3645 unconst(m->imageId).clear();
3646 m->setParentId = false;
3647 unconst(m->parentId).clear();
3648
3649 /* check the UUID */
3650 RTUUID uuid;
3651 vrc = VDGetUuid(hdd, 0, &uuid);
3652 ComAssertRCThrow(vrc, E_FAIL);
3653
3654 if (isImport)
3655 {
3656 unconst(m->id) = uuid;
3657
3658 if (m->id.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3659 // only when importing a VDMK that has no UUID, create one in memory
3660 unconst(m->id).create();
3661 }
3662 else
3663 {
3664 Assert(!m->id.isEmpty());
3665
3666 if (m->id != uuid)
3667 {
3668 lastAccessError = Utf8StrFmt(
3669 tr("UUID {%RTuuid} of the hard disk '%ls' does not match the value {%RTuuid} stored in the media registry ('%ls')"),
3670 &uuid, m->locationFull.raw(), m->id.raw(),
3671 mVirtualBox->settingsFilePath().raw());
3672 throw S_OK;
3673 }
3674 }
3675 }
3676 else
3677 {
3678 /* the backend does not support storing UUIDs within the
3679 * underlying storage so use what we store in XML */
3680
3681 /* generate an UUID for an imported UUID-less hard disk */
3682 if (isImport)
3683 {
3684 if (m->setImageId)
3685 unconst(m->id) = m->imageId;
3686 else
3687 unconst(m->id).create();
3688 }
3689 }
3690
3691 /* check the type */
3692 unsigned uImageFlags;
3693 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3694 ComAssertRCThrow(vrc, E_FAIL);
3695
3696 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3697 {
3698 RTUUID parentId;
3699 vrc = VDGetParentUuid(hdd, 0, &parentId);
3700 ComAssertRCThrow(vrc, E_FAIL);
3701
3702 if (isImport)
3703 {
3704 /* the parent must be known to us. Note that we freely
3705 * call locking methods of mVirtualBox and parent from the
3706 * write lock (breaking the {parent,child} lock order)
3707 * because there may be no concurrent access to the just
3708 * opened hard disk on ther threads yet (and init() will
3709 * fail if this method reporst MediumState_Inaccessible) */
3710
3711 Guid id = parentId;
3712 ComObjPtr<Medium> parent;
3713 rc = mVirtualBox->findHardDisk(&id, NULL,
3714 false /* aSetError */,
3715 &parent);
3716 if (FAILED(rc))
3717 {
3718 lastAccessError = Utf8StrFmt(
3719 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%ls' is not found in the media registry ('%ls')"),
3720 &parentId, m->locationFull.raw(),
3721 mVirtualBox->settingsFilePath().raw());
3722 throw S_OK;
3723 }
3724
3725 /* deassociate from VirtualBox, associate with parent */
3726
3727 mVirtualBox->removeDependentChild(this);
3728
3729 /* we set mParent & children() */
3730 AutoWriteLock treeLock(this->treeLock());
3731
3732 Assert(mParent.isNull());
3733 mParent = parent;
3734 mParent->addDependentChild(this);
3735 }
3736 else
3737 {
3738 /* we access mParent */
3739 AutoReadLock treeLock(this->treeLock());
3740
3741 /* check that parent UUIDs match. Note that there's no need
3742 * for the parent's AutoCaller (our lifetime is bound to
3743 * it) */
3744
3745 if (mParent.isNull())
3746 {
3747 lastAccessError = Utf8StrFmt(
3748 tr("Hard disk '%ls' is differencing but it is not associated with any parent hard disk in the media registry ('%ls')"),
3749 m->locationFull.raw(),
3750 mVirtualBox->settingsFilePath().raw());
3751 throw S_OK;
3752 }
3753
3754 AutoReadLock parentLock(mParent);
3755 if (mParent->state() != MediumState_Inaccessible &&
3756 mParent->id() != parentId)
3757 {
3758 lastAccessError = Utf8StrFmt(
3759 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')"),
3760 &parentId, m->locationFull.raw(),
3761 mParent->id().raw(),
3762 mVirtualBox->settingsFilePath().raw());
3763 throw S_OK;
3764 }
3765
3766 /// @todo NEWMEDIA what to do if the parent is not
3767 /// accessible while the diff is? Probably, nothing. The
3768 /// real code will detect the mismatch anyway.
3769 }
3770 }
3771
3772 m->size = VDGetFileSize(hdd, 0);
3773 m->logicalSize = VDGetSize(hdd, 0) / _1M;
3774
3775 success = true;
3776 }
3777 catch (HRESULT aRC)
3778 {
3779 rc = aRC;
3780 }
3781
3782 VDDestroy(hdd);
3783
3784 }
3785 catch (HRESULT aRC)
3786 {
3787 rc = aRC;
3788 }
3789
3790 alock.enter();
3791
3792 if (success)
3793 m->lastAccessError.setNull();
3794 else
3795 {
3796 m->lastAccessError = lastAccessError;
3797 LogWarningFunc(("'%ls' is not accessible (error='%ls', rc=%Rhrc, vrc=%Rrc)\n",
3798 m->locationFull.raw(), m->lastAccessError.raw(),
3799 rc, vrc));
3800 }
3801
3802 /* inform other callers if there are any */
3803 if (m->queryInfoCallers > 0)
3804 {
3805 RTSemEventMultiSignal(m->queryInfoSem);
3806 }
3807 else
3808 {
3809 /* delete the semaphore ourselves */
3810 RTSemEventMultiDestroy(m->queryInfoSem);
3811 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
3812 }
3813
3814 if (tempStateSet)
3815 {
3816 /* Set the proper state according to the result of the check */
3817 if (success)
3818 m->state = MediumState_Created;
3819 else
3820 m->state = MediumState_Inaccessible;
3821 }
3822 else
3823 {
3824 /* we're locked, use a special field to store the result */
3825 m->accessibleInLock = success;
3826 }
3827
3828 return rc;
3829}
3830
3831/**
3832 * Sets the extended error info according to the current media state.
3833 *
3834 * @note Must be called from under this object's write or read lock.
3835 */
3836HRESULT Medium::setStateError()
3837{
3838 HRESULT rc = E_FAIL;
3839
3840 switch (m->state)
3841 {
3842 case MediumState_NotCreated:
3843 {
3844 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3845 tr("Storage for the medium '%ls' is not created"),
3846 m->locationFull.raw());
3847 break;
3848 }
3849 case MediumState_Created:
3850 {
3851 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3852 tr("Storage for the medium '%ls' is already created"),
3853 m->locationFull.raw());
3854 break;
3855 }
3856 case MediumState_LockedRead:
3857 {
3858 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3859 tr("Medium '%ls' is locked for reading by another task"),
3860 m->locationFull.raw());
3861 break;
3862 }
3863 case MediumState_LockedWrite:
3864 {
3865 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3866 tr("Medium '%ls' is locked for writing by another task"),
3867 m->locationFull.raw());
3868 break;
3869 }
3870 case MediumState_Inaccessible:
3871 {
3872 AssertMsg(!m->lastAccessError.isEmpty(),
3873 ("There must always be a reason for Inaccessible"));
3874
3875 /* be in sync with Console::powerUpThread() */
3876 if (!m->lastAccessError.isEmpty())
3877 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3878 tr("Medium '%ls' is not accessible. %ls"),
3879 m->locationFull.raw(), m->lastAccessError.raw());
3880 else
3881 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3882 tr("Medium '%ls' is not accessible"),
3883 m->locationFull.raw());
3884 break;
3885 }
3886 case MediumState_Creating:
3887 {
3888 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3889 tr("Storage for the medium '%ls' is being created"),
3890 m->locationFull.raw(), m->lastAccessError.raw());
3891 break;
3892 }
3893 case MediumState_Deleting:
3894 {
3895 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3896 tr("Storage for the medium '%ls' is being deleted"),
3897 m->locationFull.raw(), m->lastAccessError.raw());
3898 break;
3899 }
3900 default:
3901 {
3902 AssertFailed();
3903 break;
3904 }
3905 }
3906
3907 return rc;
3908}
3909
3910/**
3911 * Deletes the hard disk storage unit.
3912 *
3913 * If @a aProgress is not NULL but the object it points to is @c null then a new
3914 * progress object will be created and assigned to @a *aProgress on success,
3915 * otherwise the existing progress object is used. If Progress is NULL, then no
3916 * progress object is created/used at all.
3917 *
3918 * When @a aWait is @c false, this method will create a thread to perform the
3919 * delete operation asynchronously and will return immediately. Otherwise, it
3920 * will perform the operation on the calling thread and will not return to the
3921 * caller until the operation is completed. Note that @a aProgress cannot be
3922 * NULL when @a aWait is @c false (this method will assert in this case).
3923 *
3924 * @param aProgress Where to find/store a Progress object to track operation
3925 * completion.
3926 * @param aWait @c true if this method should block instead of creating
3927 * an asynchronous thread.
3928 *
3929 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
3930 * writing.
3931 */
3932HRESULT Medium::deleteStorage(ComObjPtr <Progress> *aProgress, bool aWait)
3933{
3934 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
3935
3936 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
3937 * ourselves atomically after detecting that deletion is possible to make
3938 * sure that we don't do that after another thread has done
3939 * VirtualBox::findHardDisk() but before it starts using us (provided that
3940 * it holds a mVirtualBox lock too of course). */
3941
3942 AutoWriteLock vboxLock(mVirtualBox);
3943
3944 AutoWriteLock alock(this);
3945
3946 if (!(m->formatObj->capabilities() &
3947 (MediumFormatCapabilities_CreateDynamic |
3948 MediumFormatCapabilities_CreateFixed)))
3949 return setError(VBOX_E_NOT_SUPPORTED,
3950 tr("Hard disk format '%ls' does not support storage deletion"),
3951 m->format.raw());
3952
3953 /* Note that we are fine with Inaccessible state too: a) for symmetry with
3954 * create calls and b) because it doesn't really harm to try, if it is
3955 * really inaccessibke, the delete operation will fail anyway. Accepting
3956 * Inaccessible state is especially important because all registered hard
3957 * disks are initially Inaccessible upon VBoxSVC startup until
3958 * COMGETTER(State) is called. */
3959
3960 switch (m->state)
3961 {
3962 case MediumState_Created:
3963 case MediumState_Inaccessible:
3964 break;
3965 default:
3966 return setStateError();
3967 }
3968
3969 if (m->backRefs.size() != 0)
3970 return setError(VBOX_E_OBJECT_IN_USE,
3971 tr("Hard disk '%ls' is attached to %d virtual machines"),
3972 m->locationFull.raw(), m->backRefs.size());
3973
3974 HRESULT rc = canClose();
3975 CheckComRCReturnRC(rc);
3976
3977 /* go to Deleting state before leaving the lock */
3978 m->state = MediumState_Deleting;
3979
3980 /* we need to leave this object's write lock now because of
3981 * unregisterWithVirtualBox() that locks treeLock() for writing */
3982 alock.leave();
3983
3984 /* try to remove from the list of known hard disks before performing actual
3985 * deletion (we favor the consistency of the media registry in the first
3986 * place which would have been broken if unregisterWithVirtualBox() failed
3987 * after we successfully deleted the storage) */
3988
3989 rc = unregisterWithVirtualBox();
3990
3991 alock.enter();
3992
3993 /* restore the state because we may fail below; we will set it later again*/
3994 m->state = MediumState_Created;
3995
3996 CheckComRCReturnRC(rc);
3997
3998 ComObjPtr <Progress> progress;
3999
4000 if (aProgress != NULL)
4001 {
4002 /* use the existing progress object... */
4003 progress = *aProgress;
4004
4005 /* ...but create a new one if it is null */
4006 if (progress.isNull())
4007 {
4008 progress.createObject();
4009 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4010 BstrFmt(tr("Deleting hard disk storage unit '%ls'"),
4011 m->locationFull.raw()),
4012 FALSE /* aCancelable */);
4013 CheckComRCReturnRC(rc);
4014 }
4015 }
4016
4017 std::auto_ptr <Task> task(new Task(this, progress, Task::Delete));
4018 AssertComRCReturnRC(task->autoCaller.rc());
4019
4020 if (aWait)
4021 {
4022 /* go to Deleting state before starting the task */
4023 m->state = MediumState_Deleting;
4024
4025 rc = task->runNow();
4026 }
4027 else
4028 {
4029 rc = task->startThread();
4030 CheckComRCReturnRC(rc);
4031
4032 /* go to Deleting state before leaving the lock */
4033 m->state = MediumState_Deleting;
4034 }
4035
4036 /* task is now owned (or already deleted) by taskThread() so release it */
4037 task.release();
4038
4039 if (aProgress != NULL)
4040 {
4041 /* return progress to the caller */
4042 *aProgress = progress;
4043 }
4044
4045 return rc;
4046}
4047
4048/**
4049 * Creates a new differencing storage unit using the given target hard disk's
4050 * format and the location. Note that @c aTarget must be NotCreated.
4051 *
4052 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4053 * this hard disk for reading assuming that the caller has already done so. This
4054 * is used when taking an online snaopshot (where all original hard disks are
4055 * locked for writing and must remain such). Note however that if @a aWait is
4056 * @c false and this method returns a success then the thread started by
4057 * this method will unlock the hard disk (unless it is in
4058 * MediumState_LockedWrite state) so make sure the hard disk is either in
4059 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4060 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4061 * make sure you do it yourself as needed.
4062 *
4063 * If @a aProgress is not NULL but the object it points to is @c null then a new
4064 * progress object will be created and assigned to @a *aProgress on success,
4065 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4066 * progress object is created/used at all.
4067 *
4068 * When @a aWait is @c false, this method will create a thread to perform the
4069 * create operation asynchronously and will return immediately. Otherwise, it
4070 * will perform the operation on the calling thread and will not return to the
4071 * caller until the operation is completed. Note that @a aProgress cannot be
4072 * NULL when @a aWait is @c false (this method will assert in this case).
4073 *
4074 * @param aTarget Target hard disk.
4075 * @param aVariant Precise image variant to create.
4076 * @param aProgress Where to find/store a Progress object to track operation
4077 * completion.
4078 * @param aWait @c true if this method should block instead of creating
4079 * an asynchronous thread.
4080 *
4081 * @note Locks this object and @a aTarget for writing.
4082 */
4083HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4084 MediumVariant_T aVariant,
4085 ComObjPtr<Progress> *aProgress,
4086 bool aWait)
4087{
4088 AssertReturn(!aTarget.isNull(), E_FAIL);
4089 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4090
4091 AutoCaller autoCaller(this);
4092 CheckComRCReturnRC(autoCaller.rc());
4093
4094 AutoCaller targetCaller(aTarget);
4095 CheckComRCReturnRC(targetCaller.rc());
4096
4097 AutoMultiWriteLock2 alock(this, aTarget);
4098
4099 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4100
4101 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4102 AssertReturn(m->state == MediumState_LockedRead ||
4103 m->state == MediumState_LockedWrite, E_FAIL);
4104
4105 if (aTarget->m->state != MediumState_NotCreated)
4106 return aTarget->setStateError();
4107
4108 HRESULT rc = S_OK;
4109
4110 /* check that the hard disk is not attached to any VM in the current state*/
4111 for (BackRefList::const_iterator it = m->backRefs.begin();
4112 it != m->backRefs.end(); ++ it)
4113 {
4114 if (it->inCurState)
4115 {
4116 /* Note: when a VM snapshot is being taken, all normal hard disks
4117 * attached to the VM in the current state will be, as an exception,
4118 * also associated with the snapshot which is about to create (see
4119 * SnapshotMachine::init()) before deassociating them from the
4120 * current state (which takes place only on success in
4121 * Machine::fixupHardDisks()), so that the size of snapshotIds
4122 * will be 1 in this case. The given condition is used to filter out
4123 * this legal situatinon and do not report an error. */
4124
4125 if (it->snapshotIds.size() == 0)
4126 {
4127 return setError(VBOX_E_INVALID_OBJECT_STATE,
4128 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"),
4129 m->locationFull.raw(), it->machineId.raw());
4130 }
4131
4132 Assert(it->snapshotIds.size() == 1);
4133 }
4134 }
4135
4136 ComObjPtr <Progress> progress;
4137
4138 if (aProgress != NULL)
4139 {
4140 /* use the existing progress object... */
4141 progress = *aProgress;
4142
4143 /* ...but create a new one if it is null */
4144 if (progress.isNull())
4145 {
4146 progress.createObject();
4147 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4148 BstrFmt(tr("Creating differencing hard disk storage unit '%ls'"),
4149 aTarget->m->locationFull.raw()),
4150 TRUE /* aCancelable */);
4151 CheckComRCReturnRC(rc);
4152 }
4153 }
4154
4155 /* setup task object and thread to carry out the operation
4156 * asynchronously */
4157
4158 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateDiff));
4159 AssertComRCReturnRC(task->autoCaller.rc());
4160
4161 task->setData(aTarget);
4162 task->d.variant = aVariant;
4163
4164 /* register a task (it will deregister itself when done) */
4165 ++m->numCreateDiffTasks;
4166 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4167
4168 if (aWait)
4169 {
4170 /* go to Creating state before starting the task */
4171 aTarget->m->state = MediumState_Creating;
4172
4173 rc = task->runNow();
4174 }
4175 else
4176 {
4177 rc = task->startThread();
4178 CheckComRCReturnRC(rc);
4179
4180 /* go to Creating state before leaving the lock */
4181 aTarget->m->state = MediumState_Creating;
4182 }
4183
4184 /* task is now owned (or already deleted) by taskThread() so release it */
4185 task.release();
4186
4187 if (aProgress != NULL)
4188 {
4189 /* return progress to the caller */
4190 *aProgress = progress;
4191 }
4192
4193 return rc;
4194}
4195
4196/**
4197 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4198 * disks for the merge operation.
4199 *
4200 * This method is to be called prior to calling the #mergeTo() to perform
4201 * necessary consistency checks and place involved hard disks to appropriate
4202 * states. If #mergeTo() is not called or fails, the state modifications
4203 * performed by this method must be undone by #cancelMergeTo().
4204 *
4205 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4206 * responsibility to detach the source and all intermediate hard disks before
4207 * calling #mergeTo() (which will fail otherwise).
4208 *
4209 * See #mergeTo() for more information about merging.
4210 *
4211 * @param aTarget Target hard disk.
4212 * @param aChain Where to store the created merge chain.
4213 * @param aIgnoreAttachments Don't check if the source or any intermediate
4214 * hard disk is attached to any VM.
4215 *
4216 * @note Locks treeLock() for reading. Locks this object, aTarget and all
4217 * intermediate hard disks for writing.
4218 */
4219HRESULT Medium::prepareMergeTo(Medium *aTarget,
4220 MergeChain * &aChain,
4221 bool aIgnoreAttachments /*= false*/)
4222{
4223 AssertReturn(aTarget != NULL, E_FAIL);
4224
4225 AutoCaller autoCaller(this);
4226 AssertComRCReturnRC(autoCaller.rc());
4227
4228 AutoCaller targetCaller(aTarget);
4229 AssertComRCReturnRC(targetCaller.rc());
4230
4231 aChain = NULL;
4232
4233 /* we walk the tree */
4234 AutoReadLock treeLock(this->treeLock());
4235
4236 HRESULT rc = S_OK;
4237
4238 /* detect the merge direction */
4239 bool forward;
4240 {
4241 Medium *parent = mParent;
4242 while (parent != NULL && parent != aTarget)
4243 parent = parent->mParent;
4244 if (parent == aTarget)
4245 forward = false;
4246 else
4247 {
4248 parent = aTarget->mParent;
4249 while (parent != NULL && parent != this)
4250 parent = parent->mParent;
4251 if (parent == this)
4252 forward = true;
4253 else
4254 {
4255 Bstr tgtLoc;
4256 {
4257 AutoReadLock alock(this);
4258 tgtLoc = aTarget->locationFull();
4259 }
4260
4261 AutoReadLock alock(this);
4262 return setError(E_FAIL,
4263 tr("Hard disks '%ls' and '%ls' are unrelated"),
4264 m->locationFull.raw(), tgtLoc.raw());
4265 }
4266 }
4267 }
4268
4269 /* build the chain (will do necessary checks and state changes) */
4270 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4271 aIgnoreAttachments));
4272 {
4273 Medium *last = forward ? aTarget : this;
4274 Medium *first = forward ? this : aTarget;
4275
4276 for (;;)
4277 {
4278 if (last == aTarget)
4279 rc = chain->addTarget(last);
4280 else if (last == this)
4281 rc = chain->addSource(last);
4282 else
4283 rc = chain->addIntermediate(last);
4284 CheckComRCReturnRC(rc);
4285
4286 if (last == first)
4287 break;
4288
4289 last = last->mParent;
4290 }
4291 }
4292
4293 aChain = chain.release();
4294
4295 return S_OK;
4296}
4297
4298/**
4299 * Merges this hard disk to the specified hard disk which must be either its
4300 * direct ancestor or descendant.
4301 *
4302 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4303 * get two varians of the merge operation:
4304 *
4305 * forward merge
4306 * ------------------------->
4307 * [Extra] <- SOURCE <- Intermediate <- TARGET
4308 * Any Del Del LockWr
4309 *
4310 *
4311 * backward merge
4312 * <-------------------------
4313 * TARGET <- Intermediate <- SOURCE <- [Extra]
4314 * LockWr Del Del LockWr
4315 *
4316 * Each scheme shows the involved hard disks on the hard disk chain where
4317 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4318 * the hard disk must have at a time of the mergeTo() call.
4319 *
4320 * The hard disks in the square braces may be absent (e.g. when the forward
4321 * operation takes place and SOURCE is the base hard disk, or when the backward
4322 * merge operation takes place and TARGET is the last child in the chain) but if
4323 * they present they are involved too as shown.
4324 *
4325 * Nor the source hard disk neither intermediate hard disks may be attached to
4326 * any VM directly or in the snapshot, otherwise this method will assert.
4327 *
4328 * The #prepareMergeTo() method must be called prior to this method to place all
4329 * involved to necessary states and perform other consistency checks.
4330 *
4331 * If @a aWait is @c true then this method will perform the operation on the
4332 * calling thread and will not return to the caller until the operation is
4333 * completed. When this method succeeds, all intermediate hard disk objects in
4334 * the chain will be uninitialized, the state of the target hard disk (and all
4335 * involved extra hard disks) will be restored and @a aChain will be deleted.
4336 * Note that this (source) hard disk is not uninitialized because of possible
4337 * AutoCaller instances held by the caller of this method on the current thread.
4338 * It's therefore the responsibility of the caller to call Medium::uninit()
4339 * after releasing all callers in this case!
4340 *
4341 * If @a aWait is @c false then this method will crea,te a thread to perform the
4342 * create operation asynchronously and will return immediately. If the operation
4343 * succeeds, the thread will uninitialize the source hard disk object and all
4344 * intermediate hard disk objects in the chain, reset the state of the target
4345 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4346 * operation fails, the thread will only reset the states of all involved hard
4347 * disks and delete @a aChain.
4348 *
4349 * When this method fails (regardless of the @a aWait mode), it is a caller's
4350 * responsiblity to undo state changes and delete @a aChain using
4351 * #cancelMergeTo().
4352 *
4353 * If @a aProgress is not NULL but the object it points to is @c null then a new
4354 * progress object will be created and assigned to @a *aProgress on success,
4355 * otherwise the existing progress object is used. If Progress is NULL, then no
4356 * progress object is created/used at all. Note that @a aProgress cannot be
4357 * NULL when @a aWait is @c false (this method will assert in this case).
4358 *
4359 * @param aChain Merge chain created by #prepareMergeTo().
4360 * @param aProgress Where to find/store a Progress object to track operation
4361 * completion.
4362 * @param aWait @c true if this method should block instead of creating
4363 * an asynchronous thread.
4364 *
4365 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4366 * for writing.
4367 */
4368HRESULT Medium::mergeTo(MergeChain *aChain,
4369 ComObjPtr <Progress> *aProgress,
4370 bool aWait)
4371{
4372 AssertReturn(aChain != NULL, E_FAIL);
4373 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4374
4375 AutoCaller autoCaller(this);
4376 CheckComRCReturnRC(autoCaller.rc());
4377
4378 HRESULT rc = S_OK;
4379
4380 ComObjPtr <Progress> progress;
4381
4382 if (aProgress != NULL)
4383 {
4384 /* use the existing progress object... */
4385 progress = *aProgress;
4386
4387 /* ...but create a new one if it is null */
4388 if (progress.isNull())
4389 {
4390 AutoReadLock alock(this);
4391
4392 progress.createObject();
4393 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4394 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4395 name().raw(), aChain->target()->name().raw()),
4396 TRUE /* aCancelable */);
4397 CheckComRCReturnRC(rc);
4398 }
4399 }
4400
4401 /* setup task object and thread to carry out the operation
4402 * asynchronously */
4403
4404 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4405 AssertComRCReturnRC(task->autoCaller.rc());
4406
4407 task->setData(aChain);
4408
4409 /* Note: task owns aChain (will delete it when not needed) in all cases
4410 * except when @a aWait is @c true and runNow() fails -- in this case
4411 * aChain will be left away because cancelMergeTo() will be applied by the
4412 * caller on it as it is required in the documentation above */
4413
4414 if (aWait)
4415 {
4416 rc = task->runNow();
4417 }
4418 else
4419 {
4420 rc = task->startThread();
4421 CheckComRCReturnRC(rc);
4422 }
4423
4424 /* task is now owned (or already deleted) by taskThread() so release it */
4425 task.release();
4426
4427 if (aProgress != NULL)
4428 {
4429 /* return progress to the caller */
4430 *aProgress = progress;
4431 }
4432
4433 return rc;
4434}
4435
4436/**
4437 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4438 * or fails. Frees memory occupied by @a aChain.
4439 *
4440 * @param aChain Merge chain created by #prepareMergeTo().
4441 *
4442 * @note Locks the hard disks from the chain for writing.
4443 */
4444void Medium::cancelMergeTo(MergeChain *aChain)
4445{
4446 AutoCaller autoCaller(this);
4447 AssertComRCReturnVoid(autoCaller.rc());
4448
4449 AssertReturnVoid(aChain != NULL);
4450
4451 /* the destructor will do the thing */
4452 delete aChain;
4453}
4454
4455/**
4456 * Initializes the image medium object by opening an image file at the specified
4457 * location.
4458 *
4459 * @param aVirtualBox Parent VirtualBox object.
4460 * @param aLocation Path to the image file (can be relative to the
4461 * VirtualBox home directory).
4462 * @param aId UUID of the image.
4463 */
4464HRESULT Medium::protectedInit(VirtualBox *aVirtualBox, CBSTR aLocation,
4465 const Guid &aId)
4466{
4467 LogFlowThisFunc(("aLocation='%ls', aId={%RTuuid}\n", aLocation, aId.raw()));
4468
4469 AssertReturn(aVirtualBox, E_INVALIDARG);
4470 AssertReturn(aLocation, E_INVALIDARG);
4471 AssertReturn(!aId.isEmpty(), E_INVALIDARG);
4472
4473 /* Enclose the state transition NotReady->InInit->Ready */
4474 AutoInitSpan autoInitSpan(this);
4475 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4476
4477 HRESULT rc = S_OK;
4478
4479 /* share parent weakly */
4480 unconst(mVirtualBox) = aVirtualBox;
4481
4482 /* register with parent early, since uninit() will unconditionally
4483 * unregister on failure */
4484 mVirtualBox->addDependentChild(this);
4485
4486 /* there must be a storage unit */
4487 m->state = MediumState_Created;
4488
4489 unconst(m->id) = aId;
4490 rc = setLocation(aLocation);
4491 CheckComRCReturnRC(rc);
4492
4493 LogFlowThisFunc(("m->locationFull='%ls'\n", m->locationFull.raw()));
4494
4495 /* get all the information about the medium from the file */
4496 rc = queryInfo();
4497
4498 if (SUCCEEDED(rc))
4499 {
4500 /* if the image file is not accessible, it's not acceptable for the
4501 * newly opened media so convert this into an error */
4502 if (!m->lastAccessError.isEmpty())
4503 rc = setError(VBOX_E_FILE_ERROR, Utf8Str(m->lastAccessError).c_str());
4504 }
4505
4506 /* Confirm a successful initialization when it's the case */
4507 if (SUCCEEDED(rc))
4508 autoInitSpan.setSucceeded();
4509
4510 return rc;
4511}
4512
4513/**
4514 * Initializes the image medium object by loading its data from the given
4515 * settings node.
4516 *
4517 * Note that it is assumed that this method is called only for registered media.
4518 *
4519 * @param aVirtualBox Parent VirtualBox object.
4520 * @param aImageNode Either <DVDImage> or <FloppyImage> settings node.
4521 */
4522HRESULT Medium::protectedInit(VirtualBox *aVirtualBox,
4523 const settings::Medium &data)
4524{
4525 AssertReturn(aVirtualBox, E_INVALIDARG);
4526
4527 /* Enclose the state transition NotReady->InInit->Ready */
4528 AutoInitSpan autoInitSpan(this);
4529 AssertReturn(autoInitSpan.isOk(), E_FAIL);
4530
4531 HRESULT rc = S_OK;
4532
4533 /* share parent weakly */
4534 unconst(mVirtualBox) = aVirtualBox;
4535
4536 /* register with parent early, since uninit() will unconditionally
4537 * unregister on failure */
4538 mVirtualBox->addDependentChild(this);
4539
4540 /* see below why we don't call queryInfo() (and therefore treat the medium
4541 * as inaccessible for now */
4542 m->state = MediumState_Inaccessible;
4543
4544 /* required */
4545 unconst(m->id) = data.uuid;
4546 /* required */
4547 rc = setLocation(data.strLocation);
4548 CheckComRCReturnRC(rc);
4549
4550 m->description = data.strDescription;
4551
4552 LogFlowThisFunc(("m->locationFull='%ls', m->id={%RTuuid}\n",
4553 m->locationFull.raw(), m->id.raw()));
4554
4555 /* Don't call queryInfo() for registered media to prevent the calling
4556 * thread (i.e. the VirtualBox server startup thread) from an unexpected
4557 * freeze but mark it as initially inaccessible instead. The vital UUID and
4558 * location properties are read from the registry file above; to get the
4559 * actual state and the rest of the data, the user will have to call
4560 * COMGETTER(State).*/
4561
4562 /* Confirm a successful initialization when it's the case */
4563 if (SUCCEEDED(rc))
4564 autoInitSpan.setSucceeded();
4565
4566 return rc;
4567}
4568
4569/**
4570 * Uninitializes the instance.
4571 *
4572 * Called either from FinalRelease() or by the parent when it gets destroyed.
4573 */
4574void Medium::protectedUninit()
4575{
4576 LogFlowThisFunc(("\n"));
4577
4578 /* Enclose the state transition Ready->InUninit->NotReady */
4579 AutoUninitSpan autoUninitSpan(this);
4580 if (autoUninitSpan.uninitDone())
4581 return;
4582
4583 mVirtualBox->removeDependentChild(this);
4584
4585 unconst(mVirtualBox).setNull();
4586}
4587
4588// private methods
4589////////////////////////////////////////////////////////////////////////////////
4590
4591/**
4592 * Checks that the format ID is valid and sets it on success.
4593 *
4594 * Note that this method will caller-reference the format object on success!
4595 * This reference must be released somewhere to let the MediumFormat object be
4596 * uninitialized.
4597 *
4598 * @note Must be called from under this object's write lock.
4599 */
4600HRESULT Medium::setFormat(CBSTR aFormat)
4601{
4602 /* get the format object first */
4603 {
4604 AutoReadLock propsLock(mVirtualBox->systemProperties());
4605
4606 unconst(m->formatObj)
4607 = mVirtualBox->systemProperties()->mediumFormat(aFormat);
4608 if (m->formatObj.isNull())
4609 return setError(E_INVALIDARG,
4610 tr("Invalid hard disk storage format '%ls'"), aFormat);
4611
4612 /* reference the format permanently to prevent its unexpected
4613 * uninitialization */
4614 HRESULT rc = m->formatObj->addCaller();
4615 AssertComRCReturnRC(rc);
4616
4617 /* get properties (preinsert them as keys in the map). Note that the
4618 * map doesn't grow over the object life time since the set of
4619 * properties is meant to be constant. */
4620
4621 Assert(m->properties.empty());
4622
4623 for (MediumFormat::PropertyList::const_iterator it =
4624 m->formatObj->properties().begin();
4625 it != m->formatObj->properties().end();
4626 ++ it)
4627 {
4628 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4629 }
4630 }
4631
4632 unconst(m->format) = aFormat;
4633
4634 return S_OK;
4635}
4636
4637/**
4638 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
4639 * write lock.
4640 *
4641 * @note Also reused by Medium::Reset().
4642 *
4643 * @note Locks treeLock() for reading.
4644 */
4645HRESULT Medium::canClose()
4646{
4647 /* we access children */
4648 AutoReadLock treeLock(this->treeLock());
4649
4650 if (children().size() != 0)
4651 return setError(E_FAIL,
4652 tr("Hard disk '%ls' has %d child hard disks"),
4653 children().size());
4654
4655 return S_OK;
4656}
4657
4658/**
4659 * @note Called from within this object's AutoWriteLock.
4660 */
4661HRESULT Medium::canAttach(const Guid & /* aMachineId */,
4662 const Guid & /* aSnapshotId */)
4663{
4664 if (m->numCreateDiffTasks > 0)
4665 return setError(E_FAIL,
4666 tr("One or more differencing child hard disks are being created for the hard disk '%ls' (%u)"),
4667 m->locationFull.raw(), m->numCreateDiffTasks);
4668
4669 return S_OK;
4670}
4671
4672/**
4673 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
4674 * from under mVirtualBox write lock.
4675 *
4676 * @note Locks treeLock() for writing.
4677 */
4678HRESULT Medium::unregisterWithVirtualBox()
4679{
4680 /* Note that we need to de-associate ourselves from the parent to let
4681 * unregisterHardDisk() properly save the registry */
4682
4683 /* we modify mParent and access children */
4684 AutoWriteLock treeLock(this->treeLock());
4685
4686 const ComObjPtr<Medium, ComWeakRef> parent = mParent;
4687
4688 AssertReturn(children().size() == 0, E_FAIL);
4689
4690 if (!mParent.isNull())
4691 {
4692 /* deassociate from the parent, associate with VirtualBox */
4693 mVirtualBox->addDependentChild(this);
4694 mParent->removeDependentChild(this);
4695 mParent.setNull();
4696 }
4697
4698 HRESULT rc = E_FAIL;
4699 switch (m->devType)
4700 {
4701 case DeviceType_DVD:
4702 rc = mVirtualBox->unregisterDVDImage(this);
4703 break;
4704 case DeviceType_Floppy:
4705 rc = mVirtualBox->unregisterFloppyImage(this);
4706 break;
4707 case DeviceType_HardDisk:
4708 rc = mVirtualBox->unregisterHardDisk(this);
4709 break;
4710 default:
4711 break;
4712 }
4713
4714 if (FAILED(rc))
4715 {
4716 if (!parent.isNull())
4717 {
4718 /* re-associate with the parent as we are still relatives in the
4719 * registry */
4720 mParent = parent;
4721 mParent->addDependentChild(this);
4722 mVirtualBox->removeDependentChild(this);
4723 }
4724 }
4725
4726 return rc;
4727}
4728
4729/**
4730 * Returns the last error message collected by the vdErrorCall callback and
4731 * resets it.
4732 *
4733 * The error message is returned prepended with a dot and a space, like this:
4734 * <code>
4735 * ". <error_text> (%Rrc)"
4736 * </code>
4737 * to make it easily appendable to a more general error message. The @c %Rrc
4738 * format string is given @a aVRC as an argument.
4739 *
4740 * If there is no last error message collected by vdErrorCall or if it is a
4741 * null or empty string, then this function returns the following text:
4742 * <code>
4743 * " (%Rrc)"
4744 * </code>
4745 *
4746 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4747 * the callback isn't called by more than one thread at a time.
4748 *
4749 * @param aVRC VBox error code to use when no error message is provided.
4750 */
4751Utf8Str Medium::vdError(int aVRC)
4752{
4753 Utf8Str error;
4754
4755 if (m->vdError.isEmpty())
4756 error = Utf8StrFmt(" (%Rrc)", aVRC);
4757 else
4758 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4759
4760 m->vdError.setNull();
4761
4762 return error;
4763}
4764
4765/**
4766 * Error message callback.
4767 *
4768 * Puts the reported error message to the m->vdError field.
4769 *
4770 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4771 * the callback isn't called by more than one thread at a time.
4772 *
4773 * @param pvUser The opaque data passed on container creation.
4774 * @param rc The VBox error code.
4775 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4776 * @param pszFormat Error message format string.
4777 * @param va Error message arguments.
4778 */
4779/*static*/
4780DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4781 const char *pszFormat, va_list va)
4782{
4783 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4784
4785 Medium *that = static_cast<Medium*>(pvUser);
4786 AssertReturnVoid(that != NULL);
4787
4788 if (that->m->vdError.isEmpty())
4789 that->m->vdError =
4790 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4791 else
4792 that->m->vdError =
4793 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4794 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4795}
4796
4797/**
4798 * PFNVMPROGRESS callback handler for Task operations.
4799 *
4800 * @param uPercent Completetion precentage (0-100).
4801 * @param pvUser Pointer to the Progress instance.
4802 */
4803/*static*/
4804DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4805 void *pvUser)
4806{
4807 Medium *that = static_cast<Medium*>(pvUser);
4808 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4809
4810 if (that->m->vdProgress != NULL)
4811 {
4812 /* update the progress object, capping it at 99% as the final percent
4813 * is used for additional operations like setting the UUIDs and similar. */
4814 HRESULT rc = that->m->vdProgress->SetCurrentOperationProgress(uPercent * 99 / 100);
4815 if (FAILED(rc))
4816 {
4817 if (rc == E_FAIL)
4818 return VERR_CANCELLED;
4819 else
4820 return VERR_INVALID_STATE;
4821 }
4822 }
4823
4824 return VINF_SUCCESS;
4825}
4826
4827/* static */
4828DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4829 const char * /* pszzValid */)
4830{
4831 Medium *that = static_cast<Medium*>(pvUser);
4832 AssertReturn(that != NULL, false);
4833
4834 /* we always return true since the only keys we have are those found in
4835 * VDBACKENDINFO */
4836 return true;
4837}
4838
4839/* static */
4840DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4841 size_t *pcbValue)
4842{
4843 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4844
4845 Medium *that = static_cast<Medium*>(pvUser);
4846 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4847
4848 Data::PropertyMap::const_iterator it =
4849 that->m->properties.find(Bstr(pszName));
4850 if (it == that->m->properties.end())
4851 return VERR_CFGM_VALUE_NOT_FOUND;
4852
4853 /* we interpret null values as "no value" in Medium */
4854 if (it->second.isNull())
4855 return VERR_CFGM_VALUE_NOT_FOUND;
4856
4857 *pcbValue = it->second.length() + 1 /* include terminator */;
4858
4859 return VINF_SUCCESS;
4860}
4861
4862/* static */
4863DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4864 char *pszValue, size_t cchValue)
4865{
4866 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4867
4868 Medium *that = static_cast<Medium*>(pvUser);
4869 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4870
4871 Data::PropertyMap::const_iterator it =
4872 that->m->properties.find(Bstr(pszName));
4873 if (it == that->m->properties.end())
4874 return VERR_CFGM_VALUE_NOT_FOUND;
4875
4876 Utf8Str value = it->second;
4877 if (value.length() >= cchValue)
4878 return VERR_CFGM_NOT_ENOUGH_SPACE;
4879
4880 /* we interpret null values as "no value" in Medium */
4881 if (it->second.isNull())
4882 return VERR_CFGM_VALUE_NOT_FOUND;
4883
4884 memcpy(pszValue, value.c_str(), value.length() + 1);
4885
4886 return VINF_SUCCESS;
4887}
4888
4889/**
4890 * Thread function for time-consuming tasks.
4891 *
4892 * The Task structure passed to @a pvUser must be allocated using new and will
4893 * be freed by this method before it returns.
4894 *
4895 * @param pvUser Pointer to the Task instance.
4896 */
4897/* static */
4898DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
4899{
4900 std::auto_ptr <Task> task(static_cast <Task *>(pvUser));
4901 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
4902
4903 bool isAsync = thread != NIL_RTTHREAD;
4904
4905 Medium *that = task->that;
4906
4907 /// @todo ugly hack, fix ComAssert... later
4908 #define setError that->setError
4909
4910 /* Note: no need in AutoCaller because Task does that */
4911
4912 LogFlowFuncEnter();
4913 LogFlowFunc(("{%p}: operation=%d\n", that, task->operation));
4914
4915 HRESULT rc = S_OK;
4916
4917 switch (task->operation)
4918 {
4919 ////////////////////////////////////////////////////////////////////////
4920
4921 case Task::CreateBase:
4922 {
4923 /* The lock is also used as a signal from the task initiator (which
4924 * releases it only after RTThreadCreate()) that we can start the job */
4925 AutoWriteLock thatLock(that);
4926
4927 /* these parameters we need after creation */
4928 uint64_t size = 0, logicalSize = 0;
4929
4930 /* The object may request a specific UUID (through a special form of
4931 * the setLocation() argument). Otherwise we have to generate it */
4932 Guid id = that->m->id;
4933 bool generateUuid = id.isEmpty();
4934 if (generateUuid)
4935 {
4936 id.create();
4937 /* VirtualBox::registerHardDisk() will need UUID */
4938 unconst(that->m->id) = id;
4939 }
4940
4941 try
4942 {
4943 PVBOXHDD hdd;
4944 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
4945 ComAssertRCThrow(vrc, E_FAIL);
4946
4947 Utf8Str format(that->m->format);
4948 Utf8Str location(that->m->locationFull);
4949 /* uint64_t capabilities = */ that->m->formatObj->capabilities();
4950
4951 /* unlock before the potentially lengthy operation */
4952 Assert(that->m->state == MediumState_Creating);
4953 thatLock.leave();
4954
4955 try
4956 {
4957 /* ensure the directory exists */
4958 rc = VirtualBox::ensureFilePathExists(location);
4959 CheckComRCThrowRC(rc);
4960
4961 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
4962
4963 /* needed for vdProgressCallback */
4964 that->m->vdProgress = task->progress;
4965
4966 vrc = VDCreateBase(hdd, format.c_str(), location.c_str(),
4967 task->d.size * _1M,
4968 task->d.variant,
4969 NULL, &geo, &geo, id.raw(),
4970 VD_OPEN_FLAGS_NORMAL,
4971 NULL, that->m->vdDiskIfaces);
4972
4973 if (RT_FAILURE(vrc))
4974 {
4975 throw setError(E_FAIL,
4976 tr("Could not create the hard disk storage unit '%s'%s"),
4977 location.raw(), that->vdError(vrc).raw());
4978 }
4979
4980 size = VDGetFileSize(hdd, 0);
4981 logicalSize = VDGetSize(hdd, 0) / _1M;
4982 }
4983 catch (HRESULT aRC) { rc = aRC; }
4984
4985 VDDestroy(hdd);
4986 }
4987 catch (HRESULT aRC) { rc = aRC; }
4988
4989 if (SUCCEEDED(rc))
4990 {
4991 /* register with mVirtualBox as the last step and move to
4992 * Created state only on success (leaving an orphan file is
4993 * better than breaking media registry consistency) */
4994 rc = that->mVirtualBox->registerHardDisk(that);
4995 }
4996
4997 thatLock.maybeEnter();
4998
4999 if (SUCCEEDED(rc))
5000 {
5001 that->m->state = MediumState_Created;
5002
5003 that->m->size = size;
5004 that->m->logicalSize = logicalSize;
5005 }
5006 else
5007 {
5008 /* back to NotCreated on failure */
5009 that->m->state = MediumState_NotCreated;
5010
5011 /* reset UUID to prevent it from being reused next time */
5012 if (generateUuid)
5013 unconst(that->m->id).clear();
5014 }
5015
5016 break;
5017 }
5018
5019 ////////////////////////////////////////////////////////////////////////
5020
5021 case Task::CreateDiff:
5022 {
5023 ComObjPtr<Medium> &target = task->d.target;
5024
5025 /* Lock both in {parent,child} order. The lock is also used as a
5026 * signal from the task initiator (which releases it only after
5027 * RTThreadCreate()) that we can start the job*/
5028 AutoMultiWriteLock2 thatLock(that, target);
5029
5030 uint64_t size = 0, logicalSize = 0;
5031
5032 /* The object may request a specific UUID (through a special form of
5033 * the setLocation() argument). Otherwise we have to generate it */
5034 Guid targetId = target->m->id;
5035 bool generateUuid = targetId.isEmpty();
5036 if (generateUuid)
5037 {
5038 targetId.create();
5039 /* VirtualBox::registerHardDisk() will need UUID */
5040 unconst(target->m->id) = targetId;
5041 }
5042
5043 try
5044 {
5045 PVBOXHDD hdd;
5046 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5047 ComAssertRCThrow(vrc, E_FAIL);
5048
5049 Guid id = that->m->id;
5050 Utf8Str format(that->m->format);
5051 Utf8Str location(that->m->locationFull);
5052
5053 Utf8Str targetFormat(target->m->format);
5054 Utf8Str targetLocation(target->m->locationFull);
5055
5056 Assert(target->m->state == MediumState_Creating);
5057
5058 /* Note: MediumState_LockedWrite is ok when taking an online
5059 * snapshot */
5060 Assert(that->m->state == MediumState_LockedRead ||
5061 that->m->state == MediumState_LockedWrite);
5062
5063 /* unlock before the potentially lengthy operation */
5064 thatLock.leave();
5065
5066 try
5067 {
5068 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5069 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5070 that->m->vdDiskIfaces);
5071 if (RT_FAILURE(vrc))
5072 {
5073 throw setError(E_FAIL,
5074 tr("Could not open the hard disk storage unit '%s'%s"),
5075 location.raw(), that->vdError(vrc).raw());
5076 }
5077
5078 /* ensure the target directory exists */
5079 rc = VirtualBox::ensureFilePathExists(targetLocation);
5080 CheckComRCThrowRC(rc);
5081
5082 /* needed for vdProgressCallback */
5083 that->m->vdProgress = task->progress;
5084
5085 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5086 * be on the safe side. */
5087 vrc = VDCreateDiff(hdd, targetFormat.c_str(),
5088 targetLocation.c_str(),
5089 task->d.variant,
5090 NULL, targetId.raw(),
5091 id.raw(),
5092 VD_OPEN_FLAGS_NORMAL,
5093 target->m->vdDiskIfaces,
5094 that->m->vdDiskIfaces);
5095
5096 that->m->vdProgress = NULL;
5097
5098 if (RT_FAILURE(vrc))
5099 {
5100 throw setError(E_FAIL,
5101 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5102 targetLocation.raw(), that->vdError(vrc).raw());
5103 }
5104
5105 size = VDGetFileSize(hdd, 1);
5106 logicalSize = VDGetSize(hdd, 1) / _1M;
5107 }
5108 catch (HRESULT aRC) { rc = aRC; }
5109
5110 VDDestroy(hdd);
5111 }
5112 catch (HRESULT aRC) { rc = aRC; }
5113
5114 if (SUCCEEDED(rc))
5115 {
5116 /* we set mParent & children() (note that thatLock is released
5117 * here), but lock VirtualBox first to follow the rule */
5118 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5119 that->treeLock());
5120
5121 Assert(target->mParent.isNull());
5122
5123 /* associate the child with the parent and deassociate from
5124 * VirtualBox */
5125 target->mParent = that;
5126 that->addDependentChild(target);
5127 target->mVirtualBox->removeDependentChild(target);
5128
5129 /* diffs for immutable hard disks are auto-reset by default */
5130 target->m->autoReset =
5131 that->base()->m->type == MediumType_Immutable ?
5132 TRUE : FALSE;
5133
5134 /* register with mVirtualBox as the last step and move to
5135 * Created state only on success (leaving an orphan file is
5136 * better than breaking media registry consistency) */
5137 rc = that->mVirtualBox->registerHardDisk(target);
5138
5139 if (FAILED(rc))
5140 {
5141 /* break the parent association on failure to register */
5142 target->mVirtualBox->addDependentChild(target);
5143 that->removeDependentChild(target);
5144 target->mParent.setNull();
5145 }
5146 }
5147
5148 thatLock.maybeEnter();
5149
5150 if (SUCCEEDED(rc))
5151 {
5152 target->m->state = MediumState_Created;
5153
5154 target->m->size = size;
5155 target->m->logicalSize = logicalSize;
5156 }
5157 else
5158 {
5159 /* back to NotCreated on failure */
5160 target->m->state = MediumState_NotCreated;
5161
5162 target->m->autoReset = FALSE;
5163
5164 /* reset UUID to prevent it from being reused next time */
5165 if (generateUuid)
5166 unconst(target->m->id).clear();
5167 }
5168
5169 if (isAsync)
5170 {
5171 /* unlock ourselves when done (unless in MediumState_LockedWrite
5172 * state because of taking the online snapshot*/
5173 if (that->m->state != MediumState_LockedWrite)
5174 {
5175 HRESULT rc2 = that->UnlockRead(NULL);
5176 AssertComRC(rc2);
5177 }
5178 }
5179
5180 /* deregister the task registered in createDiffStorage() */
5181 Assert(that->m->numCreateDiffTasks != 0);
5182 --that->m->numCreateDiffTasks;
5183
5184 /* Note that in sync mode, it's the caller's responsibility to
5185 * unlock the hard disk */
5186
5187 break;
5188 }
5189
5190 ////////////////////////////////////////////////////////////////////////
5191
5192 case Task::Merge:
5193 {
5194 /* The lock is also used as a signal from the task initiator (which
5195 * releases it only after RTThreadCreate()) that we can start the
5196 * job. We don't actually need the lock for anything else since the
5197 * object is protected by MediumState_Deleting and we don't modify
5198 * its sensitive fields below */
5199 {
5200 AutoWriteLock thatLock(that);
5201 }
5202
5203 MergeChain *chain = task->d.chain.get();
5204
5205#if 0
5206 LogFlow(("*** MERGE forward = %RTbool\n", chain->isForward()));
5207#endif
5208
5209 try
5210 {
5211 PVBOXHDD hdd;
5212 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5213 ComAssertRCThrow(vrc, E_FAIL);
5214
5215 try
5216 {
5217 /* Open all hard disks in the chain (they are in the
5218 * {parent,child} order in there. Note that we don't lock
5219 * objects in this chain since they must be in states
5220 * (Deleting and LockedWrite) that prevent from changing
5221 * their format and location fields from outside. */
5222
5223 for (MergeChain::const_iterator it = chain->begin();
5224 it != chain->end(); ++ it)
5225 {
5226 /* complex sanity (sane complexity) */
5227 Assert((chain->isForward() &&
5228 ((*it != chain->back() &&
5229 (*it)->m->state == MediumState_Deleting) ||
5230 (*it == chain->back() &&
5231 (*it)->m->state == MediumState_LockedWrite))) ||
5232 (!chain->isForward() &&
5233 ((*it != chain->front() &&
5234 (*it)->m->state == MediumState_Deleting) ||
5235 (*it == chain->front() &&
5236 (*it)->m->state == MediumState_LockedWrite))));
5237
5238 Assert(*it == chain->target() ||
5239 (*it)->m->backRefs.size() == 0);
5240
5241 /* open the first image with VDOPEN_FLAGS_INFO because
5242 * it's not necessarily the base one */
5243 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5244 Utf8Str((*it)->m->locationFull).c_str(),
5245 it == chain->begin() ?
5246 VD_OPEN_FLAGS_INFO : 0,
5247 (*it)->m->vdDiskIfaces);
5248 if (RT_FAILURE(vrc))
5249 throw vrc;
5250#if 0
5251 LogFlow(("*** MERGE disk = %ls\n", (*it)->m->locationFull.raw()));
5252#endif
5253 }
5254
5255 /* needed for vdProgressCallback */
5256 that->m->vdProgress = task->progress;
5257
5258 unsigned start = chain->isForward() ?
5259 0 : (unsigned)chain->size() - 1;
5260 unsigned end = chain->isForward() ?
5261 (unsigned)chain->size() - 1 : 0;
5262#if 0
5263 LogFlow(("*** MERGE from %d to %d\n", start, end));
5264#endif
5265 vrc = VDMerge(hdd, start, end, that->m->vdDiskIfaces);
5266
5267 that->m->vdProgress = NULL;
5268
5269 if (RT_FAILURE(vrc))
5270 throw vrc;
5271
5272 /* update parent UUIDs */
5273 /// @todo VDMerge should be taught to do so, including the
5274 /// multiple children case
5275 if (chain->isForward())
5276 {
5277 /* target's UUID needs to be updated (note that target
5278 * is the only image in the container on success) */
5279 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5280 if (RT_FAILURE(vrc))
5281 throw vrc;
5282 }
5283 else
5284 {
5285 /* we need to update UUIDs of all source's children
5286 * which cannot be part of the container at once so
5287 * add each one in there individually */
5288 if (chain->children().size() > 0)
5289 {
5290 for (List::const_iterator it = chain->children().begin();
5291 it != chain->children().end(); ++ it)
5292 {
5293 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5294 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5295 Utf8Str((*it)->m->locationFull).c_str(),
5296 VD_OPEN_FLAGS_INFO,
5297 (*it)->m->vdDiskIfaces);
5298 if (RT_FAILURE(vrc))
5299 throw vrc;
5300
5301 vrc = VDSetParentUuid(hdd, 1,
5302 chain->target()->m->id);
5303 if (RT_FAILURE(vrc))
5304 throw vrc;
5305
5306 vrc = VDClose(hdd, false /* fDelete */);
5307 if (RT_FAILURE(vrc))
5308 throw vrc;
5309 }
5310 }
5311 }
5312 }
5313 catch (HRESULT aRC) { rc = aRC; }
5314 catch (int aVRC)
5315 {
5316 throw setError(E_FAIL,
5317 tr("Could not merge the hard disk '%ls' to '%ls'%s"),
5318 chain->source()->m->locationFull.raw(),
5319 chain->target()->m->locationFull.raw(),
5320 that->vdError(aVRC).raw());
5321 }
5322
5323 VDDestroy(hdd);
5324 }
5325 catch (HRESULT aRC) { rc = aRC; }
5326
5327 HRESULT rc2;
5328
5329 bool saveSettingsFailed = false;
5330
5331 if (SUCCEEDED(rc))
5332 {
5333 /* all hard disks but the target were successfully deleted by
5334 * VDMerge; reparent the last one and uninitialize deleted */
5335
5336 /* we set mParent & children() (note that thatLock is released
5337 * here), but lock VirtualBox first to follow the rule */
5338 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5339 that->treeLock());
5340
5341 Medium *source = chain->source();
5342 Medium *target = chain->target();
5343
5344 if (chain->isForward())
5345 {
5346 /* first, unregister the target since it may become a base
5347 * hard disk which needs re-registration */
5348 rc2 = target->mVirtualBox->
5349 unregisterHardDisk(target, false /* aSaveSettings */);
5350 AssertComRC(rc2);
5351
5352 /* then, reparent it and disconnect the deleted branch at
5353 * both ends (chain->parent() is source's parent) */
5354 target->mParent->removeDependentChild(target);
5355 target->mParent = chain->parent();
5356 if (!target->mParent.isNull())
5357 {
5358 target->mParent->addDependentChild(target);
5359 target->mParent->removeDependentChild(source);
5360 source->mParent.setNull();
5361 }
5362 else
5363 {
5364 target->mVirtualBox->addDependentChild(target);
5365 target->mVirtualBox->removeDependentChild(source);
5366 }
5367
5368 /* then, register again */
5369 rc2 = target->mVirtualBox->
5370 registerHardDisk(target, false /* aSaveSettings */);
5371 AssertComRC(rc2);
5372 }
5373 else
5374 {
5375 Assert(target->children().size() == 1);
5376 Medium *targetChild = target->children().front();
5377
5378 /* disconnect the deleted branch at the elder end */
5379 target->removeDependentChild(targetChild);
5380 targetChild->mParent.setNull();
5381
5382 const List &children = chain->children();
5383
5384 /* reparent source's chidren and disconnect the deleted
5385 * branch at the younger end m*/
5386 if (children.size() > 0)
5387 {
5388 /* obey {parent,child} lock order */
5389 AutoWriteLock sourceLock(source);
5390
5391 for (List::const_iterator it = children.begin();
5392 it != children.end(); ++ it)
5393 {
5394 AutoWriteLock childLock(*it);
5395
5396 (*it)->mParent = target;
5397 (*it)->mParent->addDependentChild(*it);
5398 source->removeDependentChild(*it);
5399 }
5400 }
5401 }
5402
5403 /* try to save the hard disk registry */
5404 rc = that->mVirtualBox->saveSettings();
5405
5406 if (SUCCEEDED(rc))
5407 {
5408 /* unregister and uninitialize all hard disks in the chain
5409 * but the target */
5410
5411 for (MergeChain::iterator it = chain->begin();
5412 it != chain->end();)
5413 {
5414 if (*it == chain->target())
5415 {
5416 ++ it;
5417 continue;
5418 }
5419
5420 rc2 = (*it)->mVirtualBox->
5421 unregisterHardDisk(*it, false /* aSaveSettings */);
5422 AssertComRC(rc2);
5423
5424 /* now, uninitialize the deleted hard disk (note that
5425 * due to the Deleting state, uninit() will not touch
5426 * the parent-child relationship so we need to
5427 * uninitialize each disk individually) */
5428
5429 /* note that the operation initiator hard disk (which is
5430 * normally also the source hard disk) is a special case
5431 * -- there is one more caller added by Task to it which
5432 * we must release. Also, if we are in sync mode, the
5433 * caller may still hold an AutoCaller instance for it
5434 * and therefore we cannot uninit() it (it's therefore
5435 * the caller's responsibility) */
5436 if (*it == that)
5437 task->autoCaller.release();
5438
5439 /* release the caller added by MergeChain before
5440 * uninit() */
5441 (*it)->releaseCaller();
5442
5443 if (isAsync || *it != that)
5444 (*it)->uninit();
5445
5446 /* delete (to prevent uninitialization in MergeChain
5447 * dtor) and advance to the next item */
5448 it = chain->erase(it);
5449 }
5450
5451 /* Note that states of all other hard disks (target, parent,
5452 * children) will be restored by the MergeChain dtor */
5453 }
5454 else
5455 {
5456 /* too bad if we fail, but we'll need to rollback everything
5457 * we did above to at least keep the HD tree in sync with
5458 * the current registry on disk */
5459
5460 saveSettingsFailed = true;
5461
5462 /// @todo NEWMEDIA implement a proper undo
5463
5464 AssertFailed();
5465 }
5466 }
5467
5468 if (FAILED(rc))
5469 {
5470 /* Here we come if either VDMerge() failed (in which case we
5471 * assume that it tried to do everything to make a further
5472 * retry possible -- e.g. not deleted intermediate hard disks
5473 * and so on) or VirtualBox::saveSettings() failed (where we
5474 * should have the original tree but with intermediate storage
5475 * units deleted by VDMerge()). We have to only restore states
5476 * (through the MergeChain dtor) unless we are run synchronously
5477 * in which case it's the responsibility of the caller as stated
5478 * in the mergeTo() docs. The latter also implies that we
5479 * don't own the merge chain, so release it in this case. */
5480
5481 if (!isAsync)
5482 task->d.chain.release();
5483
5484 NOREF(saveSettingsFailed);
5485 }
5486
5487 break;
5488 }
5489
5490 ////////////////////////////////////////////////////////////////////////
5491
5492 case Task::Clone:
5493 {
5494 ComObjPtr<Medium> &target = task->d.target;
5495 ComObjPtr<Medium> &parent = task->d.parentDisk;
5496
5497 /* Lock all in {parent,child} order. The lock is also used as a
5498 * signal from the task initiator (which releases it only after
5499 * RTThreadCreate()) that we can start the job. */
5500 AutoMultiWriteLock3 thatLock(that, target, parent);
5501
5502 ImageChain *srcChain = task->d.source.get();
5503 ImageChain *parentChain = task->d.parent.get();
5504
5505 uint64_t size = 0, logicalSize = 0;
5506
5507 /* The object may request a specific UUID (through a special form of
5508 * the setLocation() argument). Otherwise we have to generate it */
5509 Guid targetId = target->m->id;
5510 bool generateUuid = targetId.isEmpty();
5511 if (generateUuid)
5512 {
5513 targetId.create();
5514 /* VirtualBox::registerHardDisk() will need UUID */
5515 unconst(target->m->id) = targetId;
5516 }
5517
5518 try
5519 {
5520 PVBOXHDD hdd;
5521 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5522 ComAssertRCThrow(vrc, E_FAIL);
5523
5524 try
5525 {
5526 /* Open all hard disk images in the source chain. */
5527 for (List::const_iterator it = srcChain->begin();
5528 it != srcChain->end(); ++ it)
5529 {
5530 /* sanity check */
5531 Assert((*it)->m->state == MediumState_LockedRead);
5532
5533 /** Open all images in read-only mode. */
5534 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5535 Utf8Str((*it)->m->locationFull).c_str(),
5536 VD_OPEN_FLAGS_READONLY,
5537 (*it)->m->vdDiskIfaces);
5538 if (RT_FAILURE(vrc))
5539 {
5540 throw setError(E_FAIL,
5541 tr("Could not open the hard disk storage unit '%s'%s"),
5542 Utf8Str((*it)->m->locationFull).raw(),
5543 that->vdError(vrc).raw());
5544 }
5545 }
5546
5547 /* unlock before the potentially lengthy operation */
5548 thatLock.leave();
5549
5550 Utf8Str targetFormat(target->m->format);
5551 Utf8Str targetLocation(target->m->locationFull);
5552
5553 Assert( target->m->state == MediumState_Creating
5554 || target->m->state == MediumState_LockedWrite);
5555 Assert(that->m->state == MediumState_LockedRead);
5556 Assert(parent.isNull() || parent->m->state == MediumState_LockedRead);
5557
5558 /* ensure the target directory exists */
5559 rc = VirtualBox::ensureFilePathExists(targetLocation);
5560 CheckComRCThrowRC(rc);
5561
5562 /* needed for vdProgressCallback */
5563 that->m->vdProgress = task->progress;
5564
5565 PVBOXHDD targetHdd;
5566 int vrc = VDCreate(that->m->vdDiskIfaces, &targetHdd);
5567 ComAssertRCThrow(vrc, E_FAIL);
5568
5569 try
5570 {
5571 /* Open all hard disk images in the parent chain. */
5572 for (List::const_iterator it = parentChain->begin();
5573 it != parentChain->end(); ++ it)
5574 {
5575 /* sanity check */
5576 Assert( (*it)->m->state == MediumState_LockedRead
5577 || (*it)->m->state == MediumState_LockedWrite);
5578
5579 /* Open all images in appropriate mode. */
5580 vrc = VDOpen(targetHdd, Utf8Str((*it)->m->format).c_str(),
5581 Utf8Str((*it)->m->locationFull).c_str(),
5582 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5583 (*it)->m->vdDiskIfaces);
5584 if (RT_FAILURE(vrc))
5585 {
5586 throw setError(E_FAIL,
5587 tr("Could not open the hard disk storage unit '%s'%s"),
5588 Utf8Str((*it)->m->locationFull).raw(),
5589 that->vdError(vrc).raw());
5590 }
5591 }
5592
5593 vrc = VDCopy(hdd, VD_LAST_IMAGE, targetHdd,
5594 targetFormat.c_str(),
5595 target->m->state == MediumState_Creating ? targetLocation.raw() : (char *)NULL,
5596 false, 0,
5597 task->d.variant, targetId.raw(), NULL,
5598 target->m->vdDiskIfaces,
5599 that->m->vdDiskIfaces);
5600
5601 that->m->vdProgress = NULL;
5602
5603 if (RT_FAILURE(vrc))
5604 {
5605 throw setError(E_FAIL,
5606 tr("Could not create the clone hard disk '%s'%s"),
5607 targetLocation.raw(), that->vdError(vrc).raw());
5608 }
5609 size = VDGetFileSize(targetHdd, 0);
5610 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5611 }
5612 catch (HRESULT aRC) { rc = aRC; }
5613
5614 VDDestroy(targetHdd);
5615 }
5616 catch (HRESULT aRC) { rc = aRC; }
5617
5618 VDDestroy(hdd);
5619 }
5620 catch (HRESULT aRC) { rc = aRC; }
5621
5622 /* Only do the parent changes for newly created images. */
5623 if (target->m->state == MediumState_Creating)
5624 {
5625 if (SUCCEEDED(rc))
5626 {
5627 /* we set mParent & children() (note that thatLock is released
5628 * here), but lock VirtualBox first to follow the rule */
5629 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5630 that->treeLock());
5631
5632 Assert(target->mParent.isNull());
5633
5634 if (parent)
5635 {
5636 /* associate the clone with the parent and deassociate
5637 * from VirtualBox */
5638 target->mParent = parent;
5639 parent->addDependentChild(target);
5640 target->mVirtualBox->removeDependentChild(target);
5641
5642 /* register with mVirtualBox as the last step and move to
5643 * Created state only on success (leaving an orphan file is
5644 * better than breaking media registry consistency) */
5645 rc = parent->mVirtualBox->registerHardDisk(target);
5646
5647 if (FAILED(rc))
5648 {
5649 /* break parent association on failure to register */
5650 target->mVirtualBox->addDependentChild(target);
5651 parent->removeDependentChild(target);
5652 target->mParent.setNull();
5653 }
5654 }
5655 else
5656 {
5657 /* just register */
5658 rc = that->mVirtualBox->registerHardDisk(target);
5659 }
5660 }
5661 }
5662
5663 thatLock.maybeEnter();
5664
5665 if (target->m->state == MediumState_Creating)
5666 {
5667 if (SUCCEEDED(rc))
5668 {
5669 target->m->state = MediumState_Created;
5670
5671 target->m->size = size;
5672 target->m->logicalSize = logicalSize;
5673 }
5674 else
5675 {
5676 /* back to NotCreated on failure */
5677 target->m->state = MediumState_NotCreated;
5678
5679 /* reset UUID to prevent it from being reused next time */
5680 if (generateUuid)
5681 unconst(target->m->id).clear();
5682 }
5683 }
5684
5685 /* Everything is explicitly unlocked when the task exits,
5686 * as the task destruction also destroys the source chain. */
5687
5688 /* Make sure the source chain is released early. It could happen
5689 * that we get a deadlock in Appliance::Import when Medium::Close
5690 * is called & the source chain is released at the same time. */
5691 task->d.source.reset();
5692 break;
5693 }
5694
5695 ////////////////////////////////////////////////////////////////////////
5696
5697 case Task::Delete:
5698 {
5699 /* The lock is also used as a signal from the task initiator (which
5700 * releases it only after RTThreadCreate()) that we can start the job */
5701 AutoWriteLock thatLock(that);
5702
5703 try
5704 {
5705 PVBOXHDD hdd;
5706 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5707 ComAssertRCThrow(vrc, E_FAIL);
5708
5709 Utf8Str format(that->m->format);
5710 Utf8Str location(that->m->locationFull);
5711
5712 /* unlock before the potentially lengthy operation */
5713 Assert(that->m->state == MediumState_Deleting);
5714 thatLock.leave();
5715
5716 try
5717 {
5718 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5719 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5720 that->m->vdDiskIfaces);
5721 if (RT_SUCCESS(vrc))
5722 vrc = VDClose(hdd, true /* fDelete */);
5723
5724 if (RT_FAILURE(vrc))
5725 {
5726 throw setError(E_FAIL,
5727 tr("Could not delete the hard disk storage unit '%s'%s"),
5728 location.raw(), that->vdError(vrc).raw());
5729 }
5730
5731 }
5732 catch (HRESULT aRC) { rc = aRC; }
5733
5734 VDDestroy(hdd);
5735 }
5736 catch (HRESULT aRC) { rc = aRC; }
5737
5738 thatLock.maybeEnter();
5739
5740 /* go to the NotCreated state even on failure since the storage
5741 * may have been already partially deleted and cannot be used any
5742 * more. One will be able to manually re-open the storage if really
5743 * needed to re-register it. */
5744 that->m->state = MediumState_NotCreated;
5745
5746 /* Reset UUID to prevent Create* from reusing it again */
5747 unconst(that->m->id).clear();
5748
5749 break;
5750 }
5751
5752 case Task::Reset:
5753 {
5754 /* The lock is also used as a signal from the task initiator (which
5755 * releases it only after RTThreadCreate()) that we can start the job */
5756 AutoWriteLock thatLock(that);
5757
5758 /// @todo Below we use a pair of delete/create operations to reset
5759 /// the diff contents but the most efficient way will of course be
5760 /// to add a VDResetDiff() API call
5761
5762 uint64_t size = 0, logicalSize = 0;
5763
5764 try
5765 {
5766 PVBOXHDD hdd;
5767 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5768 ComAssertRCThrow(vrc, E_FAIL);
5769
5770 Guid id = that->m->id;
5771 Utf8Str format(that->m->format);
5772 Utf8Str location(that->m->locationFull);
5773
5774 Guid parentId = that->mParent->m->id;
5775 Utf8Str parentFormat(that->mParent->m->format);
5776 Utf8Str parentLocation(that->mParent->m->locationFull);
5777
5778 Assert(that->m->state == MediumState_LockedWrite);
5779
5780 /* unlock before the potentially lengthy operation */
5781 thatLock.leave();
5782
5783 try
5784 {
5785 /* first, delete the storage unit */
5786 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5787 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5788 that->m->vdDiskIfaces);
5789 if (RT_SUCCESS(vrc))
5790 vrc = VDClose(hdd, true /* fDelete */);
5791
5792 if (RT_FAILURE(vrc))
5793 {
5794 throw setError(E_FAIL,
5795 tr("Could not delete the hard disk storage unit '%s'%s"),
5796 location.raw(), that->vdError(vrc).raw());
5797 }
5798
5799 /* next, create it again */
5800 vrc = VDOpen(hdd, parentFormat.c_str(), parentLocation.c_str(),
5801 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5802 that->m->vdDiskIfaces);
5803 if (RT_FAILURE(vrc))
5804 {
5805 throw setError(E_FAIL,
5806 tr("Could not open the hard disk storage unit '%s'%s"),
5807 parentLocation.raw(), that->vdError(vrc).raw());
5808 }
5809
5810 /* needed for vdProgressCallback */
5811 that->m->vdProgress = task->progress;
5812
5813 vrc = VDCreateDiff(hdd, format.c_str(), location.c_str(),
5814 /// @todo use the same image variant as before
5815 VD_IMAGE_FLAGS_NONE,
5816 NULL, id.raw(),
5817 parentId.raw(),
5818 VD_OPEN_FLAGS_NORMAL,
5819 that->m->vdDiskIfaces,
5820 that->m->vdDiskIfaces);
5821
5822 that->m->vdProgress = NULL;
5823
5824 if (RT_FAILURE(vrc))
5825 {
5826 throw setError(E_FAIL,
5827 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5828 location.raw(), that->vdError(vrc).raw());
5829 }
5830
5831 size = VDGetFileSize(hdd, 1);
5832 logicalSize = VDGetSize(hdd, 1) / _1M;
5833 }
5834 catch (HRESULT aRC) { rc = aRC; }
5835
5836 VDDestroy(hdd);
5837 }
5838 catch (HRESULT aRC) { rc = aRC; }
5839
5840 thatLock.enter();
5841
5842 that->m->size = size;
5843 that->m->logicalSize = logicalSize;
5844
5845 if (isAsync)
5846 {
5847 /* unlock ourselves when done */
5848 HRESULT rc2 = that->UnlockWrite(NULL);
5849 AssertComRC(rc2);
5850 }
5851
5852 /* Note that in sync mode, it's the caller's responsibility to
5853 * unlock the hard disk */
5854
5855 break;
5856 }
5857
5858 ////////////////////////////////////////////////////////////////////////
5859
5860 case Task::Compact:
5861 {
5862 /* Lock all in {parent,child} order. The lock is also used as a
5863 * signal from the task initiator (which releases it only after
5864 * RTThreadCreate()) that we can start the job. */
5865 AutoWriteLock thatLock(that);
5866
5867 ImageChain *imgChain = task->d.images.get();
5868
5869 try
5870 {
5871 PVBOXHDD hdd;
5872 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5873 ComAssertRCThrow(vrc, E_FAIL);
5874
5875 try
5876 {
5877 /* Open all hard disk images in the chain. */
5878 List::const_iterator last = imgChain->end();
5879 last--;
5880 for (List::const_iterator it = imgChain->begin();
5881 it != imgChain->end(); ++ it)
5882 {
5883 /* sanity check */
5884 if (it == last)
5885 Assert((*it)->m->state == MediumState_LockedWrite);
5886 else
5887 Assert((*it)->m->state == MediumState_LockedRead);
5888
5889 /** Open all images but last in read-only mode. */
5890 vrc = VDOpen(hdd, Utf8Str((*it)->m->format).c_str(),
5891 Utf8Str((*it)->m->locationFull).c_str(),
5892 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5893 (*it)->m->vdDiskIfaces);
5894 if (RT_FAILURE(vrc))
5895 {
5896 throw setError(E_FAIL,
5897 tr("Could not open the hard disk storage unit '%s'%s"),
5898 Utf8Str((*it)->m->locationFull).raw(),
5899 that->vdError(vrc).raw());
5900 }
5901 }
5902
5903 /* unlock before the potentially lengthy operation */
5904 thatLock.leave();
5905
5906 Assert(that->m->state == MediumState_LockedWrite);
5907
5908 /* needed for vdProgressCallback */
5909 that->m->vdProgress = task->progress;
5910
5911 vrc = VDCompact(hdd, VD_LAST_IMAGE, that->m->vdDiskIfaces);
5912
5913 that->m->vdProgress = NULL;
5914
5915 if (RT_FAILURE(vrc))
5916 {
5917 if (vrc == VERR_NOT_SUPPORTED)
5918 throw setError(VBOX_E_NOT_SUPPORTED,
5919 tr("Compacting is not supported yet for hard disk '%s'"),
5920 Utf8Str(that->m->locationFull).raw());
5921 else if (vrc == VERR_NOT_IMPLEMENTED)
5922 throw setError(E_NOTIMPL,
5923 tr("Compacting is not implemented, hard disk '%s'"),
5924 Utf8Str(that->m->locationFull).raw());
5925 else
5926 throw setError(E_FAIL,
5927 tr("Could not compact hard disk '%s'%s"),
5928 Utf8Str(that->m->locationFull).raw(),
5929 that->vdError(vrc).raw());
5930 }
5931 }
5932 catch (HRESULT aRC) { rc = aRC; }
5933
5934 VDDestroy(hdd);
5935 }
5936 catch (HRESULT aRC) { rc = aRC; }
5937
5938 /* Everything is explicitly unlocked when the task exits,
5939 * as the task destruction also destroys the image chain. */
5940
5941 break;
5942 }
5943
5944 default:
5945 AssertFailedReturn(VERR_GENERAL_FAILURE);
5946 }
5947
5948 /* complete the progress if run asynchronously */
5949 if (isAsync)
5950 {
5951 if (!task->progress.isNull())
5952 task->progress->notifyComplete(rc);
5953 }
5954 else
5955 {
5956 task->rc = rc;
5957 }
5958
5959 LogFlowFunc(("rc=%Rhrc\n", rc));
5960 LogFlowFuncLeave();
5961
5962 return VINF_SUCCESS;
5963
5964 /// @todo ugly hack, fix ComAssert... later
5965 #undef setError
5966}
5967
5968/* 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