VirtualBox

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

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

comment typo

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