VirtualBox

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

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

Main: make backrefs code a bit more readable + add backrefs logging, fix deleteSnapshot() progress bars, but deleteSnapshot() is still broken

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