VirtualBox

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

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

Main: change Medium member variables from Bstr to Utf8 for better debugging and less conversions

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