VirtualBox

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

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

Main: fix deleteSnapshot failure: medium backreferences were not updated correctly after attachment of a new disk so that detachFrom() failed if the disk had been attached in the same session (e.g. through takeSnapshot())

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 188.8 KB
Line 
1/* $Id: MediumImpl.cpp 24509 2009-11-09 14:59:05Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008-2009 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "MediumImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27#include "VirtualBoxImpl.h"
28
29#include "Logging.h"
30
31#include <VBox/com/array.h>
32#include <VBox/com/SupportErrorInfo.h>
33
34#include <VBox/err.h>
35#include <VBox/settings.h>
36
37#include <iprt/param.h>
38#include <iprt/path.h>
39#include <iprt/file.h>
40#include <iprt/tcp.h>
41
42#include <VBox/VBoxHDD.h>
43
44#include <algorithm>
45
46////////////////////////////////////////////////////////////////////////////////
47//
48// Medium data definition
49//
50////////////////////////////////////////////////////////////////////////////////
51
52/** Describes how a machine refers to this image. */
53struct BackRef
54{
55 /** Equality predicate for stdc++. */
56 struct EqualsTo : public std::unary_function <BackRef, bool>
57 {
58 explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
59
60 bool operator()(const argument_type &aThat) const
61 {
62 return aThat.machineId == machineId;
63 }
64
65 const Guid machineId;
66 };
67
68 typedef std::list<Guid> GuidList;
69
70 BackRef(const Guid &aMachineId,
71 const Guid &aSnapshotId = Guid::Empty)
72 : machineId(aMachineId),
73 fInCurState(aSnapshotId.isEmpty())
74 {
75 if (!aSnapshotId.isEmpty())
76 llSnapshotIds.push_back(aSnapshotId);
77 }
78
79 Guid machineId;
80 bool fInCurState : 1;
81 GuidList llSnapshotIds;
82};
83
84typedef std::list<BackRef> BackRefList;
85
86struct Medium::Data
87{
88 Data()
89 : state(MediumState_NotCreated),
90 size(0),
91 readers(0),
92 preLockState(MediumState_NotCreated),
93 queryInfoSem(NIL_RTSEMEVENTMULTI),
94 queryInfoRunning(false),
95 type(MediumType_Normal),
96 devType(DeviceType_HardDisk),
97 logicalSize(0),
98 hddOpenMode(OpenReadWrite),
99 autoReset(false),
100 setImageId(false),
101 setParentId(false),
102 hostDrive(FALSE),
103 implicit(false),
104 numCreateDiffTasks(0),
105 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->fInCurState)
1796 ++size;
1797
1798 if (size > 0)
1799 {
1800 snapshotIds.reset(size);
1801
1802 size_t j = 0;
1803 if (it->fInCurState)
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 the caller has not supplied a snapshot ID, then we're attaching
2692 // to a machine a medium which represents the machine's current state,
2693 // so set the flag
2694 if (aSnapshotId.isEmpty())
2695 {
2696 /* sanity: no duplicate attachments */
2697 AssertReturn(!it->fInCurState, E_FAIL);
2698 it->fInCurState = true;
2699
2700 return S_OK;
2701 }
2702
2703 // otherwise: a snapshot medium is being attached
2704
2705 /* sanity: no duplicate attachments */
2706 for (BackRef::GuidList::const_iterator jt = it->llSnapshotIds.begin();
2707 jt != it->llSnapshotIds.end();
2708 ++jt)
2709 {
2710 const Guid &idOldSnapshot = *jt;
2711
2712 if (idOldSnapshot == aSnapshotId)
2713 return setError(E_FAIL,
2714 tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by snapshot '%RTuuid'"),
2715 m->strLocationFull.raw(),
2716 m->id.raw(),
2717 aSnapshotId.raw(),
2718 idOldSnapshot.raw());
2719 }
2720
2721 it->llSnapshotIds.push_back(aSnapshotId);
2722 it->fInCurState = false;
2723
2724 LogFlowThisFuncLeave();
2725
2726 return S_OK;
2727}
2728
2729/**
2730 * Removes the given machine and optionally the snapshot from the list of the
2731 * objects this image is attached to.
2732 *
2733 * @param aMachineId Machine ID.
2734 * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
2735 * attachment.
2736 */
2737HRESULT Medium::detachFrom(const Guid &aMachineId,
2738 const Guid &aSnapshotId /*= Guid::Empty*/)
2739{
2740 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2741
2742 AutoCaller autoCaller(this);
2743 AssertComRCReturnRC(autoCaller.rc());
2744
2745 AutoWriteLock alock(this);
2746
2747 BackRefList::iterator it =
2748 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2749 BackRef::EqualsTo(aMachineId));
2750 AssertReturn(it != m->backRefs.end(), E_FAIL);
2751
2752 if (aSnapshotId.isEmpty())
2753 {
2754 /* remove the current state attachment */
2755 it->fInCurState = false;
2756 }
2757 else
2758 {
2759 /* remove the snapshot attachment */
2760 BackRef::GuidList::iterator jt =
2761 std::find(it->llSnapshotIds.begin(), it->llSnapshotIds.end(), aSnapshotId);
2762
2763 AssertReturn(jt != it->llSnapshotIds.end(), E_FAIL);
2764 it->llSnapshotIds.erase(jt);
2765 }
2766
2767 /* if the backref becomes empty, remove it */
2768 if (it->fInCurState == false && it->llSnapshotIds.size() == 0)
2769 m->backRefs.erase(it);
2770
2771 return S_OK;
2772}
2773
2774#ifdef DEBUG
2775/**
2776 * Debugging helper that gets called after VirtualBox initialization that writes all
2777 * machine backreferences to the debug log.
2778 */
2779void Medium::dumpBackRefs()
2780{
2781 AutoCaller autoCaller(this);
2782 AutoReadLock(this);
2783
2784 LogFlowThisFunc(("Dumping backrefs for medium '%s':\n", m->strLocationFull.raw()));
2785
2786 for (BackRefList::iterator it2 = m->backRefs.begin();
2787 it2 != m->backRefs.end();
2788 ++it2)
2789 {
2790 const BackRef &ref = *it2;
2791 LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d)\n", ref.machineId.raw(), ref.fInCurState));
2792
2793 for (BackRef::GuidList::const_iterator jt2 = it2->llSnapshotIds.begin();
2794 jt2 != it2->llSnapshotIds.end();
2795 ++jt2)
2796 {
2797 const Guid &id = *jt2;
2798 LogFlowThisFunc((" Backref from snapshot {%RTuuid}\n", id.raw()));
2799 }
2800 }
2801}
2802#endif
2803
2804/**
2805 * Internal method to return the medium's GUID. Must have caller + locking!
2806 * @return
2807 */
2808const Guid& Medium::id() const
2809{
2810 return m->id;
2811}
2812
2813/**
2814 * Internal method to return the medium's GUID. Must have caller + locking!
2815 * @return
2816 */
2817MediumState_T Medium::state() const
2818{
2819 return m->state;
2820}
2821
2822/**
2823 * Internal method to return the medium's location. Must have caller + locking!
2824 * @return
2825 */
2826const Utf8Str& Medium::location() const
2827{
2828 return m->strLocation;
2829}
2830
2831/**
2832 * Internal method to return the medium's full location. Must have caller + locking!
2833 * @return
2834 */
2835const Utf8Str& Medium::locationFull() const
2836{
2837 return m->strLocationFull;
2838}
2839
2840/**
2841 * Internal method to return the medium's size. Must have caller + locking!
2842 * @return
2843 */
2844uint64_t Medium::size() const
2845{
2846 return m->size;
2847}
2848
2849/**
2850 * Internal method to return the medium's list of backrefs. Must have caller + locking!
2851 * @return
2852 */
2853const Guid* Medium::getFirstMachineBackrefId() const
2854{
2855 if (!m->backRefs.size())
2856 return NULL;
2857
2858 return &m->backRefs.front().machineId;
2859}
2860
2861const Guid* Medium::getFirstMachineBackrefSnapshotId() const
2862{
2863 if (!m->backRefs.size())
2864 return NULL;
2865
2866 const BackRef &ref = m->backRefs.front();
2867 if (!ref.llSnapshotIds.size())
2868 return NULL;
2869
2870 return &ref.llSnapshotIds.front();
2871}
2872
2873/**
2874 * Internal method to check whether the medium is attached to the given machine. Must have caller + locking!
2875 * @return
2876 */
2877bool Medium::isAttachedTo(const Guid &aMachineId)
2878{
2879 BackRefList::iterator it =
2880 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2881 BackRef::EqualsTo(aMachineId));
2882 return it != m->backRefs.end() && it->fInCurState;
2883}
2884
2885/**
2886 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2887 * of this hard disk or any its child and updates the paths if necessary to
2888 * reflect the new location.
2889 *
2890 * @param aOldPath Old path (full).
2891 * @param aNewPath New path (full).
2892 *
2893 * @note Locks treeLock() for reading, this object and all children for writing.
2894 */
2895void Medium::updatePaths(const char *aOldPath, const char *aNewPath)
2896{
2897 AssertReturnVoid(aOldPath);
2898 AssertReturnVoid(aNewPath);
2899
2900 AutoCaller autoCaller(this);
2901 AssertComRCReturnVoid(autoCaller.rc());
2902
2903 AutoWriteLock alock(this);
2904
2905 /* we access children() */
2906 AutoReadLock treeLock(this->treeLock());
2907
2908 updatePath(aOldPath, aNewPath);
2909
2910 /* update paths of all children */
2911 for (List::const_iterator it = children().begin();
2912 it != children().end();
2913 ++ it)
2914 {
2915 (*it)->updatePaths(aOldPath, aNewPath);
2916 }
2917}
2918
2919/**
2920 * Returns the base hard disk of the hard disk chain this hard disk is part of.
2921 *
2922 * The base hard disk is found by walking up the parent-child relationship axis.
2923 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
2924 * returns itself in response to this method.
2925 *
2926 * @param aLevel Where to store the number of ancestors of this hard disk
2927 * (zero for the base), may be @c NULL.
2928 *
2929 * @note Locks treeLock() for reading.
2930 */
2931ComObjPtr<Medium> Medium::base(uint32_t *aLevel /*= NULL*/)
2932{
2933 ComObjPtr <Medium> base;
2934 uint32_t level;
2935
2936 AutoCaller autoCaller(this);
2937 AssertReturn(autoCaller.isOk(), base);
2938
2939 /* we access mParent */
2940 AutoReadLock treeLock(this->treeLock());
2941
2942 base = this;
2943 level = 0;
2944
2945 if (!mParent.isNull())
2946 {
2947 for (;;)
2948 {
2949 AutoCaller baseCaller(base);
2950 AssertReturn(baseCaller.isOk(), base);
2951
2952 if (base->mParent.isNull())
2953 break;
2954
2955 base = base->mParent;
2956 ++ level;
2957 }
2958 }
2959
2960 if (aLevel != NULL)
2961 *aLevel = level;
2962
2963 return base;
2964}
2965
2966/**
2967 * Returns @c true if this hard disk cannot be modified because it has
2968 * dependants (children) or is part of the snapshot. Related to the hard disk
2969 * type and posterity, not to the current media state.
2970 *
2971 * @note Locks this object and treeLock() for reading.
2972 */
2973bool Medium::isReadOnly()
2974{
2975 AutoCaller autoCaller(this);
2976 AssertComRCReturn(autoCaller.rc(), false);
2977
2978 AutoReadLock alock(this);
2979
2980 /* we access children */
2981 AutoReadLock treeLock(this->treeLock());
2982
2983 switch (m->type)
2984 {
2985 case MediumType_Normal:
2986 {
2987 if (children().size() != 0)
2988 return true;
2989
2990 for (BackRefList::const_iterator it = m->backRefs.begin();
2991 it != m->backRefs.end(); ++ it)
2992 if (it->llSnapshotIds.size() != 0)
2993 return true;
2994
2995 return false;
2996 }
2997 case MediumType_Immutable:
2998 {
2999 return true;
3000 }
3001 case MediumType_Writethrough:
3002 {
3003 return false;
3004 }
3005 default:
3006 break;
3007 }
3008
3009 AssertFailedReturn(false);
3010}
3011
3012/**
3013 * Saves hard disk data by appending a new <HardDisk> child node to the given
3014 * parent node which can be either <HardDisks> or <HardDisk>.
3015 *
3016 * @param data Settings struct to be updated.
3017 *
3018 * @note Locks this object, treeLock() and children for reading.
3019 */
3020HRESULT Medium::saveSettings(settings::Medium &data)
3021{
3022 AutoCaller autoCaller(this);
3023 CheckComRCReturnRC(autoCaller.rc());
3024
3025 AutoReadLock alock(this);
3026
3027 /* we access mParent */
3028 AutoReadLock treeLock(this->treeLock());
3029
3030 data.uuid = m->id;
3031 data.strLocation = m->strLocation;
3032 data.strFormat = m->strFormat;
3033
3034 /* optional, only for diffs, default is false */
3035 if (!mParent.isNull())
3036 data.fAutoReset = !!m->autoReset;
3037 else
3038 data.fAutoReset = false;
3039
3040 /* optional */
3041 data.strDescription = m->strDescription;
3042
3043 /* optional properties */
3044 data.properties.clear();
3045 for (Data::PropertyMap::const_iterator it = m->properties.begin();
3046 it != m->properties.end();
3047 ++it)
3048 {
3049 /* only save properties that have non-default values */
3050 if (!it->second.isNull())
3051 {
3052 Utf8Str name = it->first;
3053 Utf8Str value = it->second;
3054 data.properties[name] = value;
3055 }
3056 }
3057
3058 /* only for base hard disks */
3059 if (mParent.isNull())
3060 data.hdType = m->type;
3061
3062 /* save all children */
3063 for (List::const_iterator it = children().begin();
3064 it != children().end();
3065 ++it)
3066 {
3067 settings::Medium m;
3068 HRESULT rc = (*it)->saveSettings(m);
3069 AssertComRCReturnRC(rc);
3070 data.llChildren.push_back(m);
3071 }
3072
3073 return S_OK;
3074}
3075
3076/**
3077 * Compares the location of this hard disk to the given location.
3078 *
3079 * The comparison takes the location details into account. For example, if the
3080 * location is a file in the host's filesystem, a case insensitive comparison
3081 * will be performed for case insensitive filesystems.
3082 *
3083 * @param aLocation Location to compare to (as is).
3084 * @param aResult Where to store the result of comparison: 0 if locations
3085 * are equal, 1 if this object's location is greater than
3086 * the specified location, and -1 otherwise.
3087 */
3088HRESULT Medium::compareLocationTo(const char *aLocation, int &aResult)
3089{
3090 AutoCaller autoCaller(this);
3091 AssertComRCReturnRC(autoCaller.rc());
3092
3093 AutoReadLock alock(this);
3094
3095 Utf8Str locationFull(m->strLocationFull);
3096
3097 /// @todo NEWMEDIA delegate the comparison to the backend?
3098
3099 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3100 {
3101 Utf8Str location(aLocation);
3102
3103 /* For locations represented by files, append the default path if
3104 * only the name is given, and then get the full path. */
3105 if (!RTPathHavePath(aLocation))
3106 {
3107 location = Utf8StrFmt("%s%c%s",
3108 mVirtualBox->getDefaultHardDiskFolder().raw(),
3109 RTPATH_DELIMITER,
3110 aLocation);
3111 }
3112
3113 int vrc = mVirtualBox->calculateFullPath(location, location);
3114 if (RT_FAILURE(vrc))
3115 return setError(E_FAIL,
3116 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3117 location.raw(),
3118 vrc);
3119
3120 aResult = RTPathCompare(locationFull.c_str(), location.c_str());
3121 }
3122 else
3123 aResult = locationFull.compare(aLocation);
3124
3125 return S_OK;
3126}
3127
3128/**
3129 * Checks that this hard disk may be discarded and performs necessary state
3130 * changes.
3131 *
3132 * This method is to be called prior to calling the #discard() to perform
3133 * necessary consistency checks and place involved hard disks to appropriate
3134 * states. If #discard() is not called or fails, the state modifications
3135 * performed by this method must be undone by #cancelDiscard().
3136 *
3137 * See #discard() for more info about discarding hard disks.
3138 *
3139 * @param aChain Where to store the created merge chain (may return NULL
3140 * if no real merge is necessary).
3141 *
3142 * @note Locks treeLock() for reading. Locks this object, aTarget and all
3143 * intermediate hard disks for writing.
3144 */
3145HRESULT Medium::prepareDiscard(MergeChain * &aChain)
3146{
3147 AutoCaller autoCaller(this);
3148 AssertComRCReturnRC(autoCaller.rc());
3149
3150 aChain = NULL;
3151
3152 AutoWriteLock alock(this);
3153
3154 /* we access mParent & children() */
3155 AutoReadLock treeLock(this->treeLock());
3156
3157 AssertReturn(m->type == MediumType_Normal, E_FAIL);
3158
3159 if (children().size() == 0)
3160 {
3161 /* special treatment of the last hard disk in the chain: */
3162
3163 if (mParent.isNull())
3164 {
3165 /* lock only, to prevent any usage; discard() will unlock */
3166 return LockWrite(NULL);
3167 }
3168
3169 /* the differencing hard disk w/o children will be deleted, protect it
3170 * from attaching to other VMs (this is why Deleting) */
3171
3172 switch (m->state)
3173 {
3174 case MediumState_Created:
3175 m->state = MediumState_Deleting;
3176 break;
3177 default:
3178 return setStateError();
3179 }
3180
3181 /* aChain is intentionally NULL here */
3182
3183 return S_OK;
3184 }
3185
3186 /* not going multi-merge as it's too expensive */
3187 if (children().size() > 1)
3188 return setError(E_FAIL,
3189 tr ("Hard disk '%s' has more than one child hard disk (%d)"),
3190 m->strLocationFull.raw(), children().size());
3191
3192 /* this is a read-only hard disk with children; it must be associated with
3193 * exactly one snapshot (when the snapshot is being taken, none of the
3194 * current VM's hard disks may be attached to other VMs). Note that by the
3195 * time when discard() is called, there must be no any attachments at all
3196 * (the code calling prepareDiscard() should detach). */
3197 AssertReturn(m->backRefs.size() == 1, E_FAIL);
3198 AssertReturn(!m->backRefs.front().fInCurState, E_FAIL);
3199 AssertReturn(m->backRefs.front().llSnapshotIds.size() == 1, E_FAIL);
3200
3201 ComObjPtr<Medium> child = children().front();
3202
3203 /* we keep this locked, so lock the affected child to make sure the lock
3204 * order is correct when calling prepareMergeTo() */
3205 AutoWriteLock childLock(child);
3206
3207 /* delegate the rest to the profi */
3208 if (mParent.isNull())
3209 {
3210 /* base hard disk, backward merge */
3211
3212 Assert(child->m->backRefs.size() == 1);
3213 if (child->m->backRefs.front().machineId != m->backRefs.front().machineId)
3214 {
3215 /* backward merge is too tricky, we'll just detach on discard, so
3216 * lock only, to prevent any usage; discard() will only unlock
3217 * (since we return NULL in aChain) */
3218 return LockWrite(NULL);
3219 }
3220
3221 return child->prepareMergeTo(this, aChain, true /* aIgnoreAttachments */);
3222 }
3223 else
3224 {
3225 /* forward merge */
3226 return prepareMergeTo(child, aChain, true /* aIgnoreAttachments */);
3227 }
3228}
3229
3230/**
3231 * Discards this hard disk.
3232 *
3233 * Discarding the hard disk is merging its contents to its differencing child
3234 * hard disk (forward merge) or contents of its child hard disk to itself
3235 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
3236 * a differencing hard disk w/o children, then it will be simply deleted.
3237 * Calling this method on a base hard disk w/o children will do nothing and
3238 * silently succeed. If this hard disk has more than one child, the method will
3239 * currently return an error (since merging in this case would be too expensive
3240 * and result in data duplication).
3241 *
3242 * When the backward merge takes place (i.e. this hard disk is a target) then,
3243 * on success, this hard disk will automatically replace the differencing child
3244 * hard disk used as a source (which will then be deleted) in the attachment
3245 * this child hard disk is associated with. This will happen only if both hard
3246 * disks belong to the same machine because otherwise such a replace would be
3247 * too tricky and could be not expected by the other machine. Same relates to a
3248 * case when the child hard disk is not associated with any machine at all. When
3249 * the backward merge is not applied, the method behaves as if the base hard
3250 * disk were not attached at all -- i.e. simply detaches it from the machine but
3251 * leaves the hard disk chain intact.
3252 *
3253 * This method is basically a wrapper around #mergeTo() that selects the correct
3254 * merge direction and performs additional actions as described above and.
3255 *
3256 * Note that this method will not return until the merge operation is complete
3257 * (which may be quite time consuming depending on the size of the merged hard
3258 * disks).
3259 *
3260 * Note that #prepareDiscard() must be called before calling this method. If
3261 * this method returns a failure, the caller must call #cancelDiscard(). On
3262 * success, #cancelDiscard() must not be called (this method will perform all
3263 * necessary steps such as resetting states of all involved hard disks and
3264 * deleting @a aChain).
3265 *
3266 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3267 * no real merge takes place).
3268 *
3269 * @note Locks the hard disks from the chain for writing. Locks the machine
3270 * object when the backward merge takes place. Locks treeLock() lock for
3271 * reading or writing.
3272 */
3273HRESULT Medium::discard(ComObjPtr<Progress> &aProgress, ULONG ulWeight, MergeChain *aChain)
3274{
3275 AssertReturn(!aProgress.isNull(), E_FAIL);
3276
3277 ComObjPtr <Medium> hdFrom;
3278
3279 HRESULT rc = S_OK;
3280
3281 {
3282 AutoCaller autoCaller(this);
3283 AssertComRCReturnRC(autoCaller.rc());
3284
3285 aProgress->SetNextOperation(BstrFmt(tr("Discarding hard disk '%s'"), name().raw()),
3286 ulWeight); // weight
3287
3288 if (aChain == NULL)
3289 {
3290 AutoWriteLock alock(this);
3291
3292 /* we access mParent & children() */
3293 AutoReadLock treeLock(this->treeLock());
3294
3295 Assert(children().size() == 0);
3296
3297 /* special treatment of the last hard disk in the chain: */
3298
3299 if (mParent.isNull())
3300 {
3301 rc = UnlockWrite(NULL);
3302 AssertComRC(rc);
3303 return rc;
3304 }
3305
3306 /* delete the differencing hard disk w/o children */
3307
3308 Assert(m->state == MediumState_Deleting);
3309
3310 /* go back to Created since deleteStorage() expects this state */
3311 m->state = MediumState_Created;
3312
3313 hdFrom = this;
3314
3315 // deleteStorageAndWait calls unregisterWithVirtualBox which gets
3316 // a write tree lock, so don't deadlock
3317 treeLock.unlock();
3318 alock.unlock();
3319
3320 rc = deleteStorageAndWait(&aProgress);
3321 }
3322 else
3323 {
3324 hdFrom = aChain->source();
3325
3326 rc = hdFrom->mergeToAndWait(aChain, &aProgress);
3327 }
3328 }
3329
3330 if (SUCCEEDED(rc))
3331 {
3332 /* mergeToAndWait() cannot uninitialize the initiator because of
3333 * possible AutoCallers on the current thread, deleteStorageAndWait()
3334 * doesn't do it either; do it ourselves */
3335 hdFrom->uninit();
3336 }
3337
3338 return rc;
3339}
3340
3341/**
3342 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
3343 * or fails. Frees memory occupied by @a aChain.
3344 *
3345 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3346 * no real merge takes place).
3347 *
3348 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
3349 * reading.
3350 */
3351void Medium::cancelDiscard(MergeChain *aChain)
3352{
3353 AutoCaller autoCaller(this);
3354 AssertComRCReturnVoid(autoCaller.rc());
3355
3356 if (aChain == NULL)
3357 {
3358 AutoWriteLock alock(this);
3359
3360 /* we access mParent & children() */
3361 AutoReadLock treeLock(this->treeLock());
3362
3363 Assert(children().size() == 0);
3364
3365 /* special treatment of the last hard disk in the chain: */
3366
3367 if (mParent.isNull())
3368 {
3369 HRESULT rc = UnlockWrite(NULL);
3370 AssertComRC(rc);
3371 return;
3372 }
3373
3374 /* the differencing hard disk w/o children will be deleted, protect it
3375 * from attaching to other VMs (this is why Deleting) */
3376
3377 Assert(m->state == MediumState_Deleting);
3378 m->state = MediumState_Created;
3379
3380 return;
3381 }
3382
3383 /* delegate the rest to the profi */
3384 cancelMergeTo(aChain);
3385}
3386
3387/**
3388 * Returns a preferred format for differencing hard disks.
3389 */
3390Bstr Medium::preferredDiffFormat()
3391{
3392 Utf8Str strFormat;
3393
3394 AutoCaller autoCaller(this);
3395 AssertComRCReturn(autoCaller.rc(), strFormat);
3396
3397 /* m->format is const, no need to lock */
3398 strFormat = m->strFormat;
3399
3400 /* check that our own format supports diffs */
3401 if (!(m->formatObj->capabilities() & MediumFormatCapabilities_Differencing))
3402 {
3403 /* use the default format if not */
3404 AutoReadLock propsLock(mVirtualBox->systemProperties());
3405 strFormat = mVirtualBox->getDefaultHardDiskFormat();
3406 }
3407
3408 return strFormat;
3409}
3410
3411/**
3412 * Returns the medium type. Must have caller + locking!
3413 * @return
3414 */
3415MediumType_T Medium::type() const
3416{
3417 return m->type;
3418}
3419
3420/**
3421 * Returns VirtualBox::hardDiskTreeHandle(), for convenience. Don't forget
3422 * to follow these locking rules:
3423 *
3424 * 1. The write lock on this handle must be either held alone on the thread
3425 * or requested *after* the VirtualBox object lock. Mixing with other
3426 * locks is prohibited.
3427 *
3428 * 2. The read lock on this handle may be intermixed with any other lock
3429 * with the exception that it must be requested *after* the VirtualBox
3430 * object lock.
3431 */
3432RWLockHandle* Medium::treeLock()
3433{
3434 return &mVirtualBox->hardDiskTreeLockHandle();
3435}
3436
3437// private methods
3438////////////////////////////////////////////////////////////////////////////////
3439
3440/**
3441 * Returns a short version of the location attribute.
3442 *
3443 * @note Must be called from under this object's read or write lock.
3444 */
3445Utf8Str Medium::name()
3446{
3447 Utf8Str location(m->strLocationFull);
3448
3449 Utf8Str name = RTPathFilename(location.c_str());
3450 return name;
3451}
3452
3453/**
3454 * Sets the value of m->strLocation and calculates the value of m->strLocationFull.
3455 *
3456 * Treats non-FS-path locations specially, and prepends the default hard disk
3457 * folder if the given location string does not contain any path information
3458 * at all.
3459 *
3460 * Also, if the specified location is a file path that ends with '/' then the
3461 * file name part will be generated by this method automatically in the format
3462 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
3463 * and assign to this medium, and <ext> is the default extension for this
3464 * medium's storage format. Note that this procedure requires the media state to
3465 * be NotCreated and will return a failure otherwise.
3466 *
3467 * @param aLocation Location of the storage unit. If the location is a FS-path,
3468 * then it can be relative to the VirtualBox home directory.
3469 * @param aFormat Optional fallback format if it is an import and the format
3470 * cannot be determined.
3471 *
3472 * @note Must be called from under this object's write lock.
3473 */
3474HRESULT Medium::setLocation(const Utf8Str &aLocation, const Utf8Str &aFormat)
3475{
3476 AssertReturn(!aLocation.isEmpty(), E_FAIL);
3477
3478 AutoCaller autoCaller(this);
3479 AssertComRCReturnRC(autoCaller.rc());
3480
3481 /* formatObj may be null only when initializing from an existing path and
3482 * no format is known yet */
3483 AssertReturn( (!m->strFormat.isEmpty() && !m->formatObj.isNull())
3484 || ( autoCaller.state() == InInit
3485 && m->state != MediumState_NotCreated
3486 && m->id.isEmpty()
3487 && m->strFormat.isEmpty()
3488 && m->formatObj.isNull()),
3489 E_FAIL);
3490
3491 /* are we dealing with a new medium constructed using the existing
3492 * location? */
3493 bool isImport = m->strFormat.isEmpty();
3494
3495 if ( isImport
3496 || ( (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3497 && !m->hostDrive))
3498 {
3499 Guid id;
3500
3501 Utf8Str location(aLocation);
3502
3503 if (m->state == MediumState_NotCreated)
3504 {
3505 /* must be a file (formatObj must be already known) */
3506 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3507
3508 if (RTPathFilename(location.c_str()) == NULL)
3509 {
3510 /* no file name is given (either an empty string or ends with a
3511 * slash), generate a new UUID + file name if the state allows
3512 * this */
3513
3514 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3515 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3516 E_FAIL);
3517
3518 Bstr ext = m->formatObj->fileExtensions().front();
3519 ComAssertMsgRet(!ext.isEmpty(),
3520 ("Default extension must not be empty\n"),
3521 E_FAIL);
3522
3523 id.create();
3524
3525 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3526 location.raw(), id.raw(), ext.raw());
3527 }
3528 }
3529
3530 /* append the default folder if no path is given */
3531 if (!RTPathHavePath(location.c_str()))
3532 location = Utf8StrFmt("%s%c%s",
3533 mVirtualBox->getDefaultHardDiskFolder().raw(),
3534 RTPATH_DELIMITER,
3535 location.raw());
3536
3537 /* get the full file name */
3538 Utf8Str locationFull;
3539 int vrc = mVirtualBox->calculateFullPath(location, locationFull);
3540 if (RT_FAILURE(vrc))
3541 return setError(VBOX_E_FILE_ERROR,
3542 tr("Invalid medium storage file location '%s' (%Rrc)"),
3543 location.raw(), vrc);
3544
3545 /* detect the backend from the storage unit if importing */
3546 if (isImport)
3547 {
3548 char *backendName = NULL;
3549
3550 /* is it a file? */
3551 {
3552 RTFILE file;
3553 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
3554 if (RT_SUCCESS(vrc))
3555 RTFileClose(file);
3556 }
3557 if (RT_SUCCESS(vrc))
3558 {
3559 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3560 }
3561 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3562 {
3563 /* assume it's not a file, restore the original location */
3564 location = locationFull = aLocation;
3565 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3566 }
3567
3568 if (RT_FAILURE(vrc))
3569 {
3570 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3571 return setError(VBOX_E_FILE_ERROR,
3572 tr("Could not find file for the medium '%s' (%Rrc)"),
3573 locationFull.raw(), vrc);
3574 else if (aFormat.isEmpty())
3575 return setError(VBOX_E_IPRT_ERROR,
3576 tr("Could not get the storage format of the medium '%s' (%Rrc)"),
3577 locationFull.raw(), vrc);
3578 else
3579 {
3580 HRESULT rc = setFormat(Bstr(aFormat));
3581 /* setFormat() must not fail since we've just used the backend so
3582 * the format object must be there */
3583 AssertComRCReturnRC(rc);
3584 }
3585 }
3586 else
3587 {
3588 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3589
3590 HRESULT rc = setFormat(Bstr(backendName));
3591 RTStrFree(backendName);
3592
3593 /* setFormat() must not fail since we've just used the backend so
3594 * the format object must be there */
3595 AssertComRCReturnRC(rc);
3596 }
3597 }
3598
3599 /* is it still a file? */
3600 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3601 {
3602 m->strLocation = location;
3603 m->strLocationFull = locationFull;
3604
3605 if (m->state == MediumState_NotCreated)
3606 {
3607 /* assign a new UUID (this UUID will be used when calling
3608 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3609 * also do that if we didn't generate it to make sure it is
3610 * either generated by us or reset to null */
3611 unconst(m->id) = id;
3612 }
3613 }
3614 else
3615 {
3616 m->strLocation = locationFull;
3617 m->strLocationFull = locationFull;
3618 }
3619 }
3620 else
3621 {
3622 m->strLocation = aLocation;
3623 m->strLocationFull = aLocation;
3624 }
3625
3626 return S_OK;
3627}
3628
3629/**
3630 * Queries information from the image file.
3631 *
3632 * As a result of this call, the accessibility state and data members such as
3633 * size and description will be updated with the current information.
3634 *
3635 * @note This method may block during a system I/O call that checks storage
3636 * accessibility.
3637 *
3638 * @note Locks treeLock() for reading and writing (for new diff media checked
3639 * for the first time). Locks mParent for reading. Locks this object for
3640 * writing.
3641 */
3642HRESULT Medium::queryInfo()
3643{
3644 AutoWriteLock alock(this);
3645
3646 if ( m->state != MediumState_Created
3647 && m->state != MediumState_Inaccessible
3648 && m->state != MediumState_LockedRead)
3649 return E_FAIL;
3650
3651 HRESULT rc = S_OK;
3652
3653 int vrc = VINF_SUCCESS;
3654
3655 /* check if a blocking queryInfo() call is in progress on some other thread,
3656 * and wait for it to finish if so instead of querying data ourselves */
3657 if (m->queryInfoRunning)
3658 {
3659 Assert( m->state == MediumState_LockedRead
3660 || m->state == MediumState_LockedWrite);
3661
3662 alock.leave();
3663
3664 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3665
3666 alock.enter();
3667
3668 AssertRC(vrc);
3669
3670 return S_OK;
3671 }
3672
3673 bool success = false;
3674 Utf8Str lastAccessError;
3675
3676 /* are we dealing with a new medium constructed using the existing
3677 * location? */
3678 bool isImport = m->id.isEmpty();
3679 unsigned flags = VD_OPEN_FLAGS_INFO;
3680
3681 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3682 * media because that would prevent necessary modifications
3683 * when opening media of some third-party formats for the first
3684 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3685 * generate an UUID if it is missing) */
3686 if ( (m->hddOpenMode == OpenReadOnly)
3687 || !isImport
3688 )
3689 flags |= VD_OPEN_FLAGS_READONLY;
3690
3691 /* Lock the medium, which makes the behavior much more consistent */
3692 if (flags & VD_OPEN_FLAGS_READONLY)
3693 rc = LockRead(NULL);
3694 else
3695 rc = LockWrite(NULL);
3696 CheckComRCReturnRC(rc);
3697
3698 /* Copies of the input state fields which are not read-only,
3699 * as we're dropping the lock. CAUTION: be extremely careful what
3700 * you do with the contents of this medium object, as you will
3701 * create races if there are concurrent changes. */
3702 Utf8Str format(m->strFormat);
3703 Utf8Str location(m->strLocationFull);
3704 ComObjPtr<MediumFormat> formatObj = m->formatObj;
3705
3706 /* "Output" values which can't be set because the lock isn't held
3707 * at the time the values are determined. */
3708 Guid mediumId = m->id;
3709 uint64_t mediumSize = 0;
3710 uint64_t mediumLogicalSize = 0;
3711
3712 /* leave the lock before a lengthy operation */
3713 vrc = RTSemEventMultiReset(m->queryInfoSem);
3714 ComAssertRCThrow(vrc, E_FAIL);
3715 m->queryInfoRunning = true;
3716 alock.leave();
3717
3718 try
3719 {
3720 /* skip accessibility checks for host drives */
3721 if (m->hostDrive)
3722 {
3723 success = true;
3724 throw S_OK;
3725 }
3726
3727 PVBOXHDD hdd;
3728 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3729 ComAssertRCThrow(vrc, E_FAIL);
3730
3731 try
3732 {
3733 /** @todo This kind of opening of images is assuming that diff
3734 * images can be opened as base images. Should be fixed ASAP. */
3735 vrc = VDOpen(hdd,
3736 format.c_str(),
3737 location.c_str(),
3738 flags,
3739 m->vdDiskIfaces);
3740 if (RT_FAILURE(vrc))
3741 {
3742 lastAccessError = Utf8StrFmt(tr("Could not open the medium '%s'%s"),
3743 location.c_str(), vdError(vrc).c_str());
3744 throw S_OK;
3745 }
3746
3747 if (formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3748 {
3749 /* Modify the UUIDs if necessary. The associated fields are
3750 * not modified by other code, so no need to copy. */
3751 if (m->setImageId)
3752 {
3753 vrc = VDSetUuid(hdd, 0, m->imageId);
3754 ComAssertRCThrow(vrc, E_FAIL);
3755 }
3756 if (m->setParentId)
3757 {
3758 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3759 ComAssertRCThrow(vrc, E_FAIL);
3760 }
3761 /* zap the information, these are no long-term members */
3762 m->setImageId = false;
3763 unconst(m->imageId).clear();
3764 m->setParentId = false;
3765 unconst(m->parentId).clear();
3766
3767 /* check the UUID */
3768 RTUUID uuid;
3769 vrc = VDGetUuid(hdd, 0, &uuid);
3770 ComAssertRCThrow(vrc, E_FAIL);
3771
3772 if (isImport)
3773 {
3774 mediumId = uuid;
3775
3776 if (mediumId.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3777 // only when importing a VDMK that has no UUID, create one in memory
3778 mediumId.create();
3779 }
3780 else
3781 {
3782 Assert(!mediumId.isEmpty());
3783
3784 if (mediumId != uuid)
3785 {
3786 lastAccessError = Utf8StrFmt(
3787 tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"),
3788 &uuid,
3789 location.c_str(),
3790 mediumId.raw(),
3791 mVirtualBox->settingsFilePath().c_str());
3792 throw S_OK;
3793 }
3794 }
3795 }
3796 else
3797 {
3798 /* the backend does not support storing UUIDs within the
3799 * underlying storage so use what we store in XML */
3800
3801 /* generate an UUID for an imported UUID-less medium */
3802 if (isImport)
3803 {
3804 if (m->setImageId)
3805 mediumId = m->imageId;
3806 else
3807 mediumId.create();
3808 }
3809 }
3810
3811 /* check the type */
3812 unsigned uImageFlags;
3813 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3814 ComAssertRCThrow(vrc, E_FAIL);
3815
3816 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3817 {
3818 RTUUID parentId;
3819 vrc = VDGetParentUuid(hdd, 0, &parentId);
3820 ComAssertRCThrow(vrc, E_FAIL);
3821
3822 if (isImport)
3823 {
3824 /* the parent must be known to us. Note that we freely
3825 * call locking methods of mVirtualBox and parent from the
3826 * write lock (breaking the {parent,child} lock order)
3827 * because there may be no concurrent access to the just
3828 * opened hard disk on ther threads yet (and init() will
3829 * fail if this method reporst MediumState_Inaccessible) */
3830
3831 Guid id = parentId;
3832 ComObjPtr<Medium> parent;
3833 rc = mVirtualBox->findHardDisk(&id, NULL,
3834 false /* aSetError */,
3835 &parent);
3836 if (FAILED(rc))
3837 {
3838 lastAccessError = Utf8StrFmt(
3839 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%s' is not found in the media registry ('%s')"),
3840 &parentId, location.c_str(),
3841 mVirtualBox->settingsFilePath().c_str());
3842 throw S_OK;
3843 }
3844
3845 /* deassociate from VirtualBox, associate with parent */
3846
3847 mVirtualBox->removeDependentChild(this);
3848
3849 /* we set mParent & children() */
3850 AutoWriteLock treeLock(this->treeLock());
3851
3852 Assert(mParent.isNull());
3853 mParent = parent;
3854 mParent->addDependentChild(this);
3855 }
3856 else
3857 {
3858 /* we access mParent */
3859 AutoReadLock treeLock(this->treeLock());
3860
3861 /* check that parent UUIDs match. Note that there's no need
3862 * for the parent's AutoCaller (our lifetime is bound to
3863 * it) */
3864
3865 if (mParent.isNull())
3866 {
3867 lastAccessError = Utf8StrFmt(
3868 tr("Hard disk '%s' is differencing but it is not associated with any parent hard disk in the media registry ('%s')"),
3869 location.c_str(),
3870 mVirtualBox->settingsFilePath().c_str());
3871 throw S_OK;
3872 }
3873
3874 AutoReadLock parentLock(mParent);
3875 if (mParent->state() != MediumState_Inaccessible &&
3876 mParent->id() != parentId)
3877 {
3878 lastAccessError = Utf8StrFmt(
3879 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')"),
3880 &parentId, location.c_str(),
3881 mParent->id().raw(),
3882 mVirtualBox->settingsFilePath().c_str());
3883 throw S_OK;
3884 }
3885
3886 /// @todo NEWMEDIA what to do if the parent is not
3887 /// accessible while the diff is? Probably, nothing. The
3888 /// real code will detect the mismatch anyway.
3889 }
3890 }
3891
3892 mediumSize = VDGetFileSize(hdd, 0);
3893 mediumLogicalSize = VDGetSize(hdd, 0) / _1M;
3894
3895 success = true;
3896 }
3897 catch (HRESULT aRC)
3898 {
3899 rc = aRC;
3900 }
3901
3902 VDDestroy(hdd);
3903
3904 }
3905 catch (HRESULT aRC)
3906 {
3907 rc = aRC;
3908 }
3909
3910 alock.enter();
3911
3912 if (isImport)
3913 unconst(m->id) = mediumId;
3914
3915 if (success)
3916 {
3917 m->size = mediumSize;
3918 m->logicalSize = mediumLogicalSize;
3919 m->strLastAccessError.setNull();
3920 }
3921 else
3922 {
3923 m->strLastAccessError = lastAccessError;
3924 LogWarningFunc(("'%s' is not accessible (error='%s', rc=%Rhrc, vrc=%Rrc)\n",
3925 location.c_str(), m->strLastAccessError.c_str(),
3926 rc, vrc));
3927 }
3928
3929 /* inform other callers if there are any */
3930 RTSemEventMultiSignal(m->queryInfoSem);
3931 m->queryInfoRunning = false;
3932
3933 /* Set the proper state according to the result of the check */
3934 if (success)
3935 m->preLockState = MediumState_Created;
3936 else
3937 m->preLockState = MediumState_Inaccessible;
3938
3939 if (flags & VD_OPEN_FLAGS_READONLY)
3940 rc = UnlockRead(NULL);
3941 else
3942 rc = UnlockWrite(NULL);
3943 CheckComRCReturnRC(rc);
3944
3945 return rc;
3946}
3947
3948/**
3949 * Sets the extended error info according to the current media state.
3950 *
3951 * @note Must be called from under this object's write or read lock.
3952 */
3953HRESULT Medium::setStateError()
3954{
3955 HRESULT rc = E_FAIL;
3956
3957 switch (m->state)
3958 {
3959 case MediumState_NotCreated:
3960 {
3961 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3962 tr("Storage for the medium '%s' is not created"),
3963 m->strLocationFull.raw());
3964 break;
3965 }
3966 case MediumState_Created:
3967 {
3968 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3969 tr("Storage for the medium '%s' is already created"),
3970 m->strLocationFull.raw());
3971 break;
3972 }
3973 case MediumState_LockedRead:
3974 {
3975 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3976 tr("Medium '%s' is locked for reading by another task"),
3977 m->strLocationFull.raw());
3978 break;
3979 }
3980 case MediumState_LockedWrite:
3981 {
3982 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3983 tr("Medium '%s' is locked for writing by another task"),
3984 m->strLocationFull.raw());
3985 break;
3986 }
3987 case MediumState_Inaccessible:
3988 {
3989 /* be in sync with Console::powerUpThread() */
3990 if (!m->strLastAccessError.isEmpty())
3991 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3992 tr("Medium '%s' is not accessible. %s"),
3993 m->strLocationFull.raw(), m->strLastAccessError.c_str());
3994 else
3995 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3996 tr("Medium '%s' is not accessible"),
3997 m->strLocationFull.raw());
3998 break;
3999 }
4000 case MediumState_Creating:
4001 {
4002 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
4003 tr("Storage for the medium '%s' is being created"),
4004 m->strLocationFull.raw());
4005 break;
4006 }
4007 case MediumState_Deleting:
4008 {
4009 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
4010 tr("Storage for the medium '%s' is being deleted"),
4011 m->strLocationFull.raw());
4012 break;
4013 }
4014 default:
4015 {
4016 AssertFailed();
4017 break;
4018 }
4019 }
4020
4021 return rc;
4022}
4023
4024/**
4025 * Deletes the hard disk storage unit.
4026 *
4027 * If @a aProgress is not NULL but the object it points to is @c null then a new
4028 * progress object will be created and assigned to @a *aProgress on success,
4029 * otherwise the existing progress object is used. If Progress is NULL, then no
4030 * progress object is created/used at all.
4031 *
4032 * When @a aWait is @c false, this method will create a thread to perform the
4033 * delete operation asynchronously and will return immediately. Otherwise, it
4034 * will perform the operation on the calling thread and will not return to the
4035 * caller until the operation is completed. Note that @a aProgress cannot be
4036 * NULL when @a aWait is @c false (this method will assert in this case).
4037 *
4038 * @param aProgress Where to find/store a Progress object to track operation
4039 * completion.
4040 * @param aWait @c true if this method should block instead of creating
4041 * an asynchronous thread.
4042 *
4043 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
4044 * writing.
4045 */
4046HRESULT Medium::deleteStorage(ComObjPtr <Progress> *aProgress, bool aWait)
4047{
4048 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4049
4050 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
4051 * ourselves atomically after detecting that deletion is possible to make
4052 * sure that we don't do that after another thread has done
4053 * VirtualBox::findHardDisk() but before it starts using us (provided that
4054 * it holds a mVirtualBox lock too of course). */
4055
4056 AutoMultiWriteLock2 alock(mVirtualBox->lockHandle(), this->lockHandle());
4057
4058 if ( !(m->formatObj->capabilities() & ( MediumFormatCapabilities_CreateDynamic
4059 | MediumFormatCapabilities_CreateFixed)))
4060 return setError(VBOX_E_NOT_SUPPORTED,
4061 tr("Hard disk format '%s' does not support storage deletion"),
4062 m->strFormat.raw());
4063
4064 /* Note that we are fine with Inaccessible state too: a) for symmetry with
4065 * create calls and b) because it doesn't really harm to try, if it is
4066 * really inaccessible, the delete operation will fail anyway. Accepting
4067 * Inaccessible state is especially important because all registered hard
4068 * disks are initially Inaccessible upon VBoxSVC startup until
4069 * COMGETTER(State) is called. */
4070
4071 switch (m->state)
4072 {
4073 case MediumState_Created:
4074 case MediumState_Inaccessible:
4075 break;
4076 default:
4077 return setStateError();
4078 }
4079
4080 if (m->backRefs.size() != 0)
4081 {
4082 Utf8Str strMachines;
4083 for (BackRefList::const_iterator it = m->backRefs.begin();
4084 it != m->backRefs.end();
4085 ++it)
4086 {
4087 const BackRef &b = *it;
4088 if (strMachines.length())
4089 strMachines.append(", ");
4090 strMachines.append(b.machineId.toString().c_str());
4091 }
4092#ifdef DEBUG
4093 dumpBackRefs();
4094#endif
4095 return setError(VBOX_E_OBJECT_IN_USE,
4096 tr("Cannot delete storage: hard disk '%s' is still attached to the following %d virtual machine(s): %s"),
4097 m->strLocationFull.c_str(),
4098 m->backRefs.size(),
4099 strMachines.c_str());
4100 }
4101
4102 HRESULT rc = canClose();
4103 CheckComRCReturnRC(rc);
4104
4105 /* go to Deleting state before leaving the lock */
4106 m->state = MediumState_Deleting;
4107
4108 /* we need to leave this object's write lock now because of
4109 * unregisterWithVirtualBox() that locks treeLock() for writing */
4110 alock.leave();
4111
4112 /* try to remove from the list of known hard disks before performing actual
4113 * deletion (we favor the consistency of the media registry in the first
4114 * place which would have been broken if unregisterWithVirtualBox() failed
4115 * after we successfully deleted the storage) */
4116
4117 rc = unregisterWithVirtualBox();
4118
4119 alock.enter();
4120
4121 /* restore the state because we may fail below; we will set it later again*/
4122 m->state = MediumState_Created;
4123
4124 CheckComRCReturnRC(rc);
4125
4126 ComObjPtr <Progress> progress;
4127
4128 if (aProgress != NULL)
4129 {
4130 /* use the existing progress object... */
4131 progress = *aProgress;
4132
4133 /* ...but create a new one if it is null */
4134 if (progress.isNull())
4135 {
4136 progress.createObject();
4137 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4138 BstrFmt(tr("Deleting hard disk storage unit '%s'"),
4139 m->strLocationFull.raw()),
4140 FALSE /* aCancelable */);
4141 CheckComRCReturnRC(rc);
4142 }
4143 }
4144
4145 std::auto_ptr <Task> task(new Task(this, progress, Task::Delete));
4146 AssertComRCReturnRC(task->autoCaller.rc());
4147
4148 if (aWait)
4149 {
4150 /* go to Deleting state before starting the task */
4151 m->state = MediumState_Deleting;
4152
4153 rc = task->runNow();
4154 }
4155 else
4156 {
4157 rc = task->startThread();
4158 CheckComRCReturnRC(rc);
4159
4160 /* go to Deleting state before leaving the lock */
4161 m->state = MediumState_Deleting;
4162 }
4163
4164 /* task is now owned (or already deleted) by taskThread() so release it */
4165 task.release();
4166
4167 if (aProgress != NULL)
4168 {
4169 /* return progress to the caller */
4170 *aProgress = progress;
4171 }
4172
4173 return rc;
4174}
4175
4176/**
4177 * Creates a new differencing storage unit using the given target hard disk's
4178 * format and the location. Note that @c aTarget must be NotCreated.
4179 *
4180 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4181 * this hard disk for reading assuming that the caller has already done so. This
4182 * is used when taking an online snaopshot (where all original hard disks are
4183 * locked for writing and must remain such). Note however that if @a aWait is
4184 * @c false and this method returns a success then the thread started by
4185 * this method will unlock the hard disk (unless it is in
4186 * MediumState_LockedWrite state) so make sure the hard disk is either in
4187 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4188 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4189 * make sure you do it yourself as needed.
4190 *
4191 * If @a aProgress is not NULL but the object it points to is @c null then a new
4192 * progress object will be created and assigned to @a *aProgress on success,
4193 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4194 * progress object is created/used at all.
4195 *
4196 * When @a aWait is @c false, this method will create a thread to perform the
4197 * create operation asynchronously and will return immediately. Otherwise, it
4198 * will perform the operation on the calling thread and will not return to the
4199 * caller until the operation is completed. Note that @a aProgress cannot be
4200 * NULL when @a aWait is @c false (this method will assert in this case).
4201 *
4202 * @param aTarget Target hard disk.
4203 * @param aVariant Precise image variant to create.
4204 * @param aProgress Where to find/store a Progress object to track operation
4205 * completion.
4206 * @param aWait @c true if this method should block instead of creating
4207 * an asynchronous thread.
4208 *
4209 * @note Locks this object and @a aTarget for writing.
4210 */
4211HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4212 MediumVariant_T aVariant,
4213 ComObjPtr<Progress> *aProgress,
4214 bool aWait)
4215{
4216 AssertReturn(!aTarget.isNull(), E_FAIL);
4217 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4218
4219 AutoCaller autoCaller(this);
4220 CheckComRCReturnRC(autoCaller.rc());
4221
4222 AutoCaller targetCaller(aTarget);
4223 CheckComRCReturnRC(targetCaller.rc());
4224
4225 AutoMultiWriteLock2 alock(this, aTarget);
4226
4227 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4228
4229 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4230 AssertReturn(m->state == MediumState_LockedRead ||
4231 m->state == MediumState_LockedWrite, E_FAIL);
4232
4233 if (aTarget->m->state != MediumState_NotCreated)
4234 return aTarget->setStateError();
4235
4236 HRESULT rc = S_OK;
4237
4238 /* check that the hard disk is not attached to any VM in the current state*/
4239 for (BackRefList::const_iterator it = m->backRefs.begin();
4240 it != m->backRefs.end(); ++ it)
4241 {
4242 if (it->fInCurState)
4243 {
4244 /* Note: when a VM snapshot is being taken, all normal hard disks
4245 * attached to the VM in the current state will be, as an exception,
4246 * also associated with the snapshot which is about to create (see
4247 * SnapshotMachine::init()) before deassociating them from the
4248 * current state (which takes place only on success in
4249 * Machine::fixupHardDisks()), so that the size of snapshotIds
4250 * will be 1 in this case. The given condition is used to filter out
4251 * this legal situatinon and do not report an error. */
4252
4253 if (it->llSnapshotIds.size() == 0)
4254 {
4255 return setError(VBOX_E_INVALID_OBJECT_STATE,
4256 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"),
4257 m->strLocationFull.raw(), it->machineId.raw());
4258 }
4259
4260 Assert(it->llSnapshotIds.size() == 1);
4261 }
4262 }
4263
4264 ComObjPtr <Progress> progress;
4265
4266 if (aProgress != NULL)
4267 {
4268 /* use the existing progress object... */
4269 progress = *aProgress;
4270
4271 /* ...but create a new one if it is null */
4272 if (progress.isNull())
4273 {
4274 progress.createObject();
4275 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4276 BstrFmt(tr("Creating differencing hard disk storage unit '%s'"),
4277 aTarget->m->strLocationFull.raw()),
4278 TRUE /* aCancelable */);
4279 CheckComRCReturnRC(rc);
4280 }
4281 }
4282
4283 /* setup task object and thread to carry out the operation
4284 * asynchronously */
4285
4286 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateDiff));
4287 AssertComRCReturnRC(task->autoCaller.rc());
4288
4289 task->setData(aTarget);
4290 task->d.variant = aVariant;
4291
4292 /* register a task (it will deregister itself when done) */
4293 ++m->numCreateDiffTasks;
4294 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4295
4296 if (aWait)
4297 {
4298 /* go to Creating state before starting the task */
4299 aTarget->m->state = MediumState_Creating;
4300
4301 rc = task->runNow();
4302 }
4303 else
4304 {
4305 rc = task->startThread();
4306 CheckComRCReturnRC(rc);
4307
4308 /* go to Creating state before leaving the lock */
4309 aTarget->m->state = MediumState_Creating;
4310 }
4311
4312 /* task is now owned (or already deleted) by taskThread() so release it */
4313 task.release();
4314
4315 if (aProgress != NULL)
4316 {
4317 /* return progress to the caller */
4318 *aProgress = progress;
4319 }
4320
4321 return rc;
4322}
4323
4324/**
4325 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4326 * disks for the merge operation.
4327 *
4328 * This method is to be called prior to calling the #mergeTo() to perform
4329 * necessary consistency checks and place involved hard disks to appropriate
4330 * states. If #mergeTo() is not called or fails, the state modifications
4331 * performed by this method must be undone by #cancelMergeTo().
4332 *
4333 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4334 * responsibility to detach the source and all intermediate hard disks before
4335 * calling #mergeTo() (which will fail otherwise).
4336 *
4337 * See #mergeTo() for more information about merging.
4338 *
4339 * @param aTarget Target hard disk.
4340 * @param aChain Where to store the created merge chain.
4341 * @param aIgnoreAttachments Don't check if the source or any intermediate
4342 * hard disk is attached to any VM.
4343 *
4344 * @note Locks treeLock() for reading. Locks this object, aTarget and all
4345 * intermediate hard disks for writing.
4346 */
4347HRESULT Medium::prepareMergeTo(Medium *aTarget,
4348 MergeChain * &aChain,
4349 bool aIgnoreAttachments /*= false*/)
4350{
4351 AssertReturn(aTarget != NULL, E_FAIL);
4352
4353 AutoCaller autoCaller(this);
4354 AssertComRCReturnRC(autoCaller.rc());
4355
4356 AutoCaller targetCaller(aTarget);
4357 AssertComRCReturnRC(targetCaller.rc());
4358
4359 aChain = NULL;
4360
4361 /* we walk the tree */
4362 AutoReadLock treeLock(this->treeLock());
4363
4364 HRESULT rc = S_OK;
4365
4366 /* detect the merge direction */
4367 bool forward;
4368 {
4369 Medium *parent = mParent;
4370 while (parent != NULL && parent != aTarget)
4371 parent = parent->mParent;
4372 if (parent == aTarget)
4373 forward = false;
4374 else
4375 {
4376 parent = aTarget->mParent;
4377 while (parent != NULL && parent != this)
4378 parent = parent->mParent;
4379 if (parent == this)
4380 forward = true;
4381 else
4382 {
4383 Utf8Str tgtLoc;
4384 {
4385 AutoReadLock alock(this);
4386 tgtLoc = aTarget->locationFull();
4387 }
4388
4389 AutoReadLock alock(this);
4390 return setError(E_FAIL,
4391 tr("Hard disks '%s' and '%s' are unrelated"),
4392 m->strLocationFull.raw(), tgtLoc.raw());
4393 }
4394 }
4395 }
4396
4397 /* build the chain (will do necessary checks and state changes) */
4398 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4399 aIgnoreAttachments));
4400 {
4401 Medium *last = forward ? aTarget : this;
4402 Medium *first = forward ? this : aTarget;
4403
4404 for (;;)
4405 {
4406 if (last == aTarget)
4407 rc = chain->addTarget(last);
4408 else if (last == this)
4409 rc = chain->addSource(last);
4410 else
4411 rc = chain->addIntermediate(last);
4412 CheckComRCReturnRC(rc);
4413
4414 if (last == first)
4415 break;
4416
4417 last = last->mParent;
4418 }
4419 }
4420
4421 aChain = chain.release();
4422
4423 return S_OK;
4424}
4425
4426/**
4427 * Merges this hard disk to the specified hard disk which must be either its
4428 * direct ancestor or descendant.
4429 *
4430 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4431 * get two varians of the merge operation:
4432 *
4433 * forward merge
4434 * ------------------------->
4435 * [Extra] <- SOURCE <- Intermediate <- TARGET
4436 * Any Del Del LockWr
4437 *
4438 *
4439 * backward merge
4440 * <-------------------------
4441 * TARGET <- Intermediate <- SOURCE <- [Extra]
4442 * LockWr Del Del LockWr
4443 *
4444 * Each scheme shows the involved hard disks on the hard disk chain where
4445 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4446 * the hard disk must have at a time of the mergeTo() call.
4447 *
4448 * The hard disks in the square braces may be absent (e.g. when the forward
4449 * operation takes place and SOURCE is the base hard disk, or when the backward
4450 * merge operation takes place and TARGET is the last child in the chain) but if
4451 * they present they are involved too as shown.
4452 *
4453 * Nor the source hard disk neither intermediate hard disks may be attached to
4454 * any VM directly or in the snapshot, otherwise this method will assert.
4455 *
4456 * The #prepareMergeTo() method must be called prior to this method to place all
4457 * involved to necessary states and perform other consistency checks.
4458 *
4459 * If @a aWait is @c true then this method will perform the operation on the
4460 * calling thread and will not return to the caller until the operation is
4461 * completed. When this method succeeds, all intermediate hard disk objects in
4462 * the chain will be uninitialized, the state of the target hard disk (and all
4463 * involved extra hard disks) will be restored and @a aChain will be deleted.
4464 * Note that this (source) hard disk is not uninitialized because of possible
4465 * AutoCaller instances held by the caller of this method on the current thread.
4466 * It's therefore the responsibility of the caller to call Medium::uninit()
4467 * after releasing all callers in this case!
4468 *
4469 * If @a aWait is @c false then this method will crea,te a thread to perform the
4470 * create operation asynchronously and will return immediately. If the operation
4471 * succeeds, the thread will uninitialize the source hard disk object and all
4472 * intermediate hard disk objects in the chain, reset the state of the target
4473 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4474 * operation fails, the thread will only reset the states of all involved hard
4475 * disks and delete @a aChain.
4476 *
4477 * When this method fails (regardless of the @a aWait mode), it is a caller's
4478 * responsiblity to undo state changes and delete @a aChain using
4479 * #cancelMergeTo().
4480 *
4481 * If @a aProgress is not NULL but the object it points to is @c null then a new
4482 * progress object will be created and assigned to @a *aProgress on success,
4483 * otherwise the existing progress object is used. If Progress is NULL, then no
4484 * progress object is created/used at all. Note that @a aProgress cannot be
4485 * NULL when @a aWait is @c false (this method will assert in this case).
4486 *
4487 * @param aChain Merge chain created by #prepareMergeTo().
4488 * @param aProgress Where to find/store a Progress object to track operation
4489 * completion.
4490 * @param aWait @c true if this method should block instead of creating
4491 * an asynchronous thread.
4492 *
4493 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4494 * for writing.
4495 */
4496HRESULT Medium::mergeTo(MergeChain *aChain,
4497 ComObjPtr <Progress> *aProgress,
4498 bool aWait)
4499{
4500 AssertReturn(aChain != NULL, E_FAIL);
4501 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4502
4503 AutoCaller autoCaller(this);
4504 CheckComRCReturnRC(autoCaller.rc());
4505
4506 HRESULT rc = S_OK;
4507
4508 ComObjPtr <Progress> progress;
4509
4510 if (aProgress != NULL)
4511 {
4512 /* use the existing progress object... */
4513 progress = *aProgress;
4514
4515 /* ...but create a new one if it is null */
4516 if (progress.isNull())
4517 {
4518 AutoReadLock alock(this);
4519
4520 progress.createObject();
4521 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4522 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4523 name().raw(), aChain->target()->name().raw()),
4524 TRUE /* aCancelable */);
4525 CheckComRCReturnRC(rc);
4526 }
4527 }
4528
4529 /* setup task object and thread to carry out the operation
4530 * asynchronously */
4531
4532 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4533 AssertComRCReturnRC(task->autoCaller.rc());
4534
4535 task->setData(aChain);
4536
4537 /* Note: task owns aChain (will delete it when not needed) in all cases
4538 * except when @a aWait is @c true and runNow() fails -- in this case
4539 * aChain will be left away because cancelMergeTo() will be applied by the
4540 * caller on it as it is required in the documentation above */
4541
4542 if (aWait)
4543 {
4544 rc = task->runNow();
4545 }
4546 else
4547 {
4548 rc = task->startThread();
4549 CheckComRCReturnRC(rc);
4550 }
4551
4552 /* task is now owned (or already deleted) by taskThread() so release it */
4553 task.release();
4554
4555 if (aProgress != NULL)
4556 {
4557 /* return progress to the caller */
4558 *aProgress = progress;
4559 }
4560
4561 return rc;
4562}
4563
4564/**
4565 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4566 * or fails. Frees memory occupied by @a aChain.
4567 *
4568 * @param aChain Merge chain created by #prepareMergeTo().
4569 *
4570 * @note Locks the hard disks from the chain for writing.
4571 */
4572void Medium::cancelMergeTo(MergeChain *aChain)
4573{
4574 AutoCaller autoCaller(this);
4575 AssertComRCReturnVoid(autoCaller.rc());
4576
4577 AssertReturnVoid(aChain != NULL);
4578
4579 /* the destructor will do the thing */
4580 delete aChain;
4581}
4582
4583/**
4584 * Checks that the format ID is valid and sets it on success.
4585 *
4586 * Note that this method will caller-reference the format object on success!
4587 * This reference must be released somewhere to let the MediumFormat object be
4588 * uninitialized.
4589 *
4590 * @note Must be called from under this object's write lock.
4591 */
4592HRESULT Medium::setFormat(CBSTR aFormat)
4593{
4594 /* get the format object first */
4595 {
4596 AutoReadLock propsLock(mVirtualBox->systemProperties());
4597
4598 unconst(m->formatObj)
4599 = mVirtualBox->systemProperties()->mediumFormat(aFormat);
4600 if (m->formatObj.isNull())
4601 return setError(E_INVALIDARG,
4602 tr("Invalid hard disk storage format '%ls'"), aFormat);
4603
4604 /* reference the format permanently to prevent its unexpected
4605 * uninitialization */
4606 HRESULT rc = m->formatObj->addCaller();
4607 AssertComRCReturnRC(rc);
4608
4609 /* get properties (preinsert them as keys in the map). Note that the
4610 * map doesn't grow over the object life time since the set of
4611 * properties is meant to be constant. */
4612
4613 Assert(m->properties.empty());
4614
4615 for (MediumFormat::PropertyList::const_iterator it =
4616 m->formatObj->properties().begin();
4617 it != m->formatObj->properties().end();
4618 ++ it)
4619 {
4620 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4621 }
4622 }
4623
4624 unconst(m->strFormat) = aFormat;
4625
4626 return S_OK;
4627}
4628
4629/**
4630 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
4631 * write lock.
4632 *
4633 * @note Also reused by Medium::Reset().
4634 *
4635 * @note Locks treeLock() for reading.
4636 */
4637HRESULT Medium::canClose()
4638{
4639 /* we access children */
4640 AutoReadLock treeLock(this->treeLock());
4641
4642 if (children().size() != 0)
4643 return setError(E_FAIL,
4644 tr("Hard disk '%ls' has %d child hard disks"),
4645 children().size());
4646
4647 return S_OK;
4648}
4649
4650/**
4651 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
4652 * from under mVirtualBox write lock.
4653 *
4654 * @note Locks treeLock() for writing.
4655 */
4656HRESULT Medium::unregisterWithVirtualBox()
4657{
4658 /* Note that we need to de-associate ourselves from the parent to let
4659 * unregisterHardDisk() properly save the registry */
4660
4661 /* we modify mParent and access children */
4662 AutoWriteLock treeLock(this->treeLock());
4663
4664 const ComObjPtr<Medium, ComWeakRef> parent = mParent;
4665
4666 AssertReturn(children().size() == 0, E_FAIL);
4667
4668 if (!mParent.isNull())
4669 {
4670 /* deassociate from the parent, associate with VirtualBox */
4671 mVirtualBox->addDependentChild(this);
4672 mParent->removeDependentChild(this);
4673 mParent.setNull();
4674 }
4675
4676 HRESULT rc = E_FAIL;
4677 switch (m->devType)
4678 {
4679 case DeviceType_DVD:
4680 rc = mVirtualBox->unregisterDVDImage(this);
4681 break;
4682 case DeviceType_Floppy:
4683 rc = mVirtualBox->unregisterFloppyImage(this);
4684 break;
4685 case DeviceType_HardDisk:
4686 rc = mVirtualBox->unregisterHardDisk(this);
4687 break;
4688 default:
4689 break;
4690 }
4691
4692 if (FAILED(rc))
4693 {
4694 if (!parent.isNull())
4695 {
4696 /* re-associate with the parent as we are still relatives in the
4697 * registry */
4698 mParent = parent;
4699 mParent->addDependentChild(this);
4700 mVirtualBox->removeDependentChild(this);
4701 }
4702 }
4703
4704 return rc;
4705}
4706
4707/**
4708 * Returns the last error message collected by the vdErrorCall callback and
4709 * resets it.
4710 *
4711 * The error message is returned prepended with a dot and a space, like this:
4712 * <code>
4713 * ". <error_text> (%Rrc)"
4714 * </code>
4715 * to make it easily appendable to a more general error message. The @c %Rrc
4716 * format string is given @a aVRC as an argument.
4717 *
4718 * If there is no last error message collected by vdErrorCall or if it is a
4719 * null or empty string, then this function returns the following text:
4720 * <code>
4721 * " (%Rrc)"
4722 * </code>
4723 *
4724 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4725 * the callback isn't called by more than one thread at a time.
4726 *
4727 * @param aVRC VBox error code to use when no error message is provided.
4728 */
4729Utf8Str Medium::vdError(int aVRC)
4730{
4731 Utf8Str error;
4732
4733 if (m->vdError.isEmpty())
4734 error = Utf8StrFmt(" (%Rrc)", aVRC);
4735 else
4736 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4737
4738 m->vdError.setNull();
4739
4740 return error;
4741}
4742
4743/**
4744 * Error message callback.
4745 *
4746 * Puts the reported error message to the m->vdError field.
4747 *
4748 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4749 * the callback isn't called by more than one thread at a time.
4750 *
4751 * @param pvUser The opaque data passed on container creation.
4752 * @param rc The VBox error code.
4753 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4754 * @param pszFormat Error message format string.
4755 * @param va Error message arguments.
4756 */
4757/*static*/
4758DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4759 const char *pszFormat, va_list va)
4760{
4761 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4762
4763 Medium *that = static_cast<Medium*>(pvUser);
4764 AssertReturnVoid(that != NULL);
4765
4766 if (that->m->vdError.isEmpty())
4767 that->m->vdError =
4768 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4769 else
4770 that->m->vdError =
4771 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4772 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4773}
4774
4775/**
4776 * PFNVMPROGRESS callback handler for Task operations.
4777 *
4778 * @param uPercent Completetion precentage (0-100).
4779 * @param pvUser Pointer to the Progress instance.
4780 */
4781/*static*/
4782DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4783 void *pvUser)
4784{
4785 Medium *that = static_cast<Medium*>(pvUser);
4786 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4787
4788 if (that->m->vdProgress != NULL)
4789 {
4790 /* update the progress object, capping it at 99% as the final percent
4791 * is used for additional operations like setting the UUIDs and similar. */
4792 HRESULT rc = that->m->vdProgress->SetCurrentOperationProgress(uPercent * 99 / 100);
4793 if (FAILED(rc))
4794 {
4795 if (rc == E_FAIL)
4796 return VERR_CANCELLED;
4797 else
4798 return VERR_INVALID_STATE;
4799 }
4800 }
4801
4802 return VINF_SUCCESS;
4803}
4804
4805/* static */
4806DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4807 const char * /* pszzValid */)
4808{
4809 Medium *that = static_cast<Medium*>(pvUser);
4810 AssertReturn(that != NULL, false);
4811
4812 /* we always return true since the only keys we have are those found in
4813 * VDBACKENDINFO */
4814 return true;
4815}
4816
4817/* static */
4818DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4819 size_t *pcbValue)
4820{
4821 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4822
4823 Medium *that = static_cast<Medium*>(pvUser);
4824 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4825
4826 Data::PropertyMap::const_iterator it =
4827 that->m->properties.find(Bstr(pszName));
4828 if (it == that->m->properties.end())
4829 return VERR_CFGM_VALUE_NOT_FOUND;
4830
4831 /* we interpret null values as "no value" in Medium */
4832 if (it->second.isNull())
4833 return VERR_CFGM_VALUE_NOT_FOUND;
4834
4835 *pcbValue = it->second.length() + 1 /* include terminator */;
4836
4837 return VINF_SUCCESS;
4838}
4839
4840/* static */
4841DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4842 char *pszValue, size_t cchValue)
4843{
4844 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4845
4846 Medium *that = static_cast<Medium*>(pvUser);
4847 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4848
4849 Data::PropertyMap::const_iterator it =
4850 that->m->properties.find(Bstr(pszName));
4851 if (it == that->m->properties.end())
4852 return VERR_CFGM_VALUE_NOT_FOUND;
4853
4854 Utf8Str value = it->second;
4855 if (value.length() >= cchValue)
4856 return VERR_CFGM_NOT_ENOUGH_SPACE;
4857
4858 /* we interpret null values as "no value" in Medium */
4859 if (it->second.isNull())
4860 return VERR_CFGM_VALUE_NOT_FOUND;
4861
4862 memcpy(pszValue, value.c_str(), value.length() + 1);
4863
4864 return VINF_SUCCESS;
4865}
4866
4867/**
4868 * Thread function for time-consuming tasks.
4869 *
4870 * The Task structure passed to @a pvUser must be allocated using new and will
4871 * be freed by this method before it returns.
4872 *
4873 * @param pvUser Pointer to the Task instance.
4874 */
4875/* static */
4876DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
4877{
4878 std::auto_ptr <Task> task(static_cast <Task *>(pvUser));
4879 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
4880
4881 bool isAsync = thread != NIL_RTTHREAD;
4882
4883 Medium *that = task->that;
4884
4885 /// @todo ugly hack, fix ComAssert... later
4886 #define setError that->setError
4887
4888 /* Note: no need in AutoCaller because Task does that */
4889
4890 LogFlowFuncEnter();
4891 LogFlowFunc(("{%p}: operation=%d\n", that, task->operation));
4892
4893 HRESULT rc = S_OK;
4894
4895 switch (task->operation)
4896 {
4897 ////////////////////////////////////////////////////////////////////////
4898
4899 case Task::CreateBase:
4900 {
4901 /* The lock is also used as a signal from the task initiator (which
4902 * releases it only after RTThreadCreate()) that we can start the job */
4903 AutoWriteLock thatLock(that);
4904
4905 /* these parameters we need after creation */
4906 uint64_t size = 0, logicalSize = 0;
4907
4908 /* The object may request a specific UUID (through a special form of
4909 * the setLocation() argument). Otherwise we have to generate it */
4910 Guid id = that->m->id;
4911 bool generateUuid = id.isEmpty();
4912 if (generateUuid)
4913 {
4914 id.create();
4915 /* VirtualBox::registerHardDisk() will need UUID */
4916 unconst(that->m->id) = id;
4917 }
4918
4919 try
4920 {
4921 PVBOXHDD hdd;
4922 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
4923 ComAssertRCThrow(vrc, E_FAIL);
4924
4925 Utf8Str format(that->m->strFormat);
4926 Utf8Str location(that->m->strLocationFull);
4927 /* uint64_t capabilities = */ that->m->formatObj->capabilities();
4928
4929 /* unlock before the potentially lengthy operation */
4930 Assert(that->m->state == MediumState_Creating);
4931 thatLock.leave();
4932
4933 try
4934 {
4935 /* ensure the directory exists */
4936 rc = VirtualBox::ensureFilePathExists(location);
4937 CheckComRCThrowRC(rc);
4938
4939 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
4940
4941 /* needed for vdProgressCallback */
4942 that->m->vdProgress = task->progress;
4943
4944 vrc = VDCreateBase(hdd, format.c_str(), location.c_str(),
4945 task->d.size * _1M,
4946 task->d.variant,
4947 NULL, &geo, &geo, id.raw(),
4948 VD_OPEN_FLAGS_NORMAL,
4949 NULL, that->m->vdDiskIfaces);
4950
4951 if (RT_FAILURE(vrc))
4952 {
4953 throw setError(E_FAIL,
4954 tr("Could not create the hard disk storage unit '%s'%s"),
4955 location.raw(), that->vdError(vrc).raw());
4956 }
4957
4958 size = VDGetFileSize(hdd, 0);
4959 logicalSize = VDGetSize(hdd, 0) / _1M;
4960 }
4961 catch (HRESULT aRC) { rc = aRC; }
4962
4963 VDDestroy(hdd);
4964 }
4965 catch (HRESULT aRC) { rc = aRC; }
4966
4967 if (SUCCEEDED(rc))
4968 {
4969 /* register with mVirtualBox as the last step and move to
4970 * Created state only on success (leaving an orphan file is
4971 * better than breaking media registry consistency) */
4972 rc = that->mVirtualBox->registerHardDisk(that);
4973 }
4974
4975 thatLock.maybeEnter();
4976
4977 if (SUCCEEDED(rc))
4978 {
4979 that->m->state = MediumState_Created;
4980
4981 that->m->size = size;
4982 that->m->logicalSize = logicalSize;
4983 }
4984 else
4985 {
4986 /* back to NotCreated on failure */
4987 that->m->state = MediumState_NotCreated;
4988
4989 /* reset UUID to prevent it from being reused next time */
4990 if (generateUuid)
4991 unconst(that->m->id).clear();
4992 }
4993
4994 break;
4995 }
4996
4997 ////////////////////////////////////////////////////////////////////////
4998
4999 case Task::CreateDiff:
5000 {
5001 ComObjPtr<Medium> &target = task->d.target;
5002
5003 /* Lock both in {parent,child} order. The lock is also used as a
5004 * signal from the task initiator (which releases it only after
5005 * RTThreadCreate()) that we can start the job*/
5006 AutoMultiWriteLock2 thatLock(that, target);
5007
5008 uint64_t size = 0, logicalSize = 0;
5009
5010 /* The object may request a specific UUID (through a special form of
5011 * the setLocation() argument). Otherwise we have to generate it */
5012 Guid targetId = target->m->id;
5013 bool generateUuid = targetId.isEmpty();
5014 if (generateUuid)
5015 {
5016 targetId.create();
5017 /* VirtualBox::registerHardDisk() will need UUID */
5018 unconst(target->m->id) = targetId;
5019 }
5020
5021 try
5022 {
5023 PVBOXHDD hdd;
5024 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5025 ComAssertRCThrow(vrc, E_FAIL);
5026
5027 Guid id = that->m->id;
5028 Utf8Str format(that->m->strFormat);
5029 Utf8Str location(that->m->strLocationFull);
5030
5031 Utf8Str targetFormat(target->m->strFormat);
5032 Utf8Str targetLocation(target->m->strLocationFull);
5033
5034 Assert(target->m->state == MediumState_Creating);
5035
5036 /* Note: MediumState_LockedWrite is ok when taking an online
5037 * snapshot */
5038 Assert(that->m->state == MediumState_LockedRead ||
5039 that->m->state == MediumState_LockedWrite);
5040
5041 /* unlock before the potentially lengthy operation */
5042 thatLock.leave();
5043
5044 try
5045 {
5046 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5047 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5048 that->m->vdDiskIfaces);
5049 if (RT_FAILURE(vrc))
5050 {
5051 throw setError(E_FAIL,
5052 tr("Could not open the hard disk storage unit '%s'%s"),
5053 location.raw(), that->vdError(vrc).raw());
5054 }
5055
5056 /* ensure the target directory exists */
5057 rc = VirtualBox::ensureFilePathExists(targetLocation);
5058 CheckComRCThrowRC(rc);
5059
5060 /* needed for vdProgressCallback */
5061 that->m->vdProgress = task->progress;
5062
5063 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5064 * be on the safe side. */
5065 vrc = VDCreateDiff(hdd, targetFormat.c_str(),
5066 targetLocation.c_str(),
5067 task->d.variant,
5068 NULL, targetId.raw(),
5069 id.raw(),
5070 VD_OPEN_FLAGS_NORMAL,
5071 target->m->vdDiskIfaces,
5072 that->m->vdDiskIfaces);
5073
5074 that->m->vdProgress = NULL;
5075
5076 if (RT_FAILURE(vrc))
5077 {
5078 throw setError(E_FAIL,
5079 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5080 targetLocation.raw(), that->vdError(vrc).raw());
5081 }
5082
5083 size = VDGetFileSize(hdd, 1);
5084 logicalSize = VDGetSize(hdd, 1) / _1M;
5085 }
5086 catch (HRESULT aRC) { rc = aRC; }
5087
5088 VDDestroy(hdd);
5089 }
5090 catch (HRESULT aRC) { rc = aRC; }
5091
5092 if (SUCCEEDED(rc))
5093 {
5094 /* we set mParent & children() (note that thatLock is released
5095 * here), but lock VirtualBox first to follow the rule */
5096 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5097 that->treeLock());
5098
5099 Assert(target->mParent.isNull());
5100
5101 /* associate the child with the parent and deassociate from
5102 * VirtualBox */
5103 target->mParent = that;
5104 that->addDependentChild(target);
5105 target->mVirtualBox->removeDependentChild(target);
5106
5107 /* diffs for immutable hard disks are auto-reset by default */
5108 target->m->autoReset =
5109 that->base()->m->type == MediumType_Immutable ?
5110 TRUE : FALSE;
5111
5112 /* register with mVirtualBox as the last step and move to
5113 * Created state only on success (leaving an orphan file is
5114 * better than breaking media registry consistency) */
5115 rc = that->mVirtualBox->registerHardDisk(target);
5116
5117 if (FAILED(rc))
5118 {
5119 /* break the parent association on failure to register */
5120 target->mVirtualBox->addDependentChild(target);
5121 that->removeDependentChild(target);
5122 target->mParent.setNull();
5123 }
5124 }
5125
5126 thatLock.maybeEnter();
5127
5128 if (SUCCEEDED(rc))
5129 {
5130 target->m->state = MediumState_Created;
5131
5132 target->m->size = size;
5133 target->m->logicalSize = logicalSize;
5134 }
5135 else
5136 {
5137 /* back to NotCreated on failure */
5138 target->m->state = MediumState_NotCreated;
5139
5140 target->m->autoReset = FALSE;
5141
5142 /* reset UUID to prevent it from being reused next time */
5143 if (generateUuid)
5144 unconst(target->m->id).clear();
5145 }
5146
5147 if (isAsync)
5148 {
5149 /* unlock ourselves when done (unless in MediumState_LockedWrite
5150 * state because of taking the online snapshot*/
5151 if (that->m->state != MediumState_LockedWrite)
5152 {
5153 HRESULT rc2 = that->UnlockRead(NULL);
5154 AssertComRC(rc2);
5155 }
5156 }
5157
5158 /* deregister the task registered in createDiffStorage() */
5159 Assert(that->m->numCreateDiffTasks != 0);
5160 --that->m->numCreateDiffTasks;
5161
5162 /* Note that in sync mode, it's the caller's responsibility to
5163 * unlock the hard disk */
5164
5165 break;
5166 }
5167
5168 ////////////////////////////////////////////////////////////////////////
5169
5170 case Task::Merge:
5171 {
5172 /* The lock is also used as a signal from the task initiator (which
5173 * releases it only after RTThreadCreate()) that we can start the
5174 * job. We don't actually need the lock for anything else since the
5175 * object is protected by MediumState_Deleting and we don't modify
5176 * its sensitive fields below */
5177 {
5178 AutoWriteLock thatLock(that);
5179 }
5180
5181 MergeChain *chain = task->d.chain.get();
5182
5183#if 0
5184 LogFlow(("*** MERGE forward = %RTbool\n", chain->isForward()));
5185#endif
5186
5187 try
5188 {
5189 PVBOXHDD hdd;
5190 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5191 ComAssertRCThrow(vrc, E_FAIL);
5192
5193 try
5194 {
5195 /* Open all hard disks in the chain (they are in the
5196 * {parent,child} order in there. Note that we don't lock
5197 * objects in this chain since they must be in states
5198 * (Deleting and LockedWrite) that prevent from changing
5199 * their format and location fields from outside. */
5200
5201 for (MergeChain::const_iterator it = chain->begin();
5202 it != chain->end(); ++ it)
5203 {
5204 /* complex sanity (sane complexity) */
5205 Assert((chain->isForward() &&
5206 ((*it != chain->back() &&
5207 (*it)->m->state == MediumState_Deleting) ||
5208 (*it == chain->back() &&
5209 (*it)->m->state == MediumState_LockedWrite))) ||
5210 (!chain->isForward() &&
5211 ((*it != chain->front() &&
5212 (*it)->m->state == MediumState_Deleting) ||
5213 (*it == chain->front() &&
5214 (*it)->m->state == MediumState_LockedWrite))));
5215
5216 Assert(*it == chain->target() ||
5217 (*it)->m->backRefs.size() == 0);
5218
5219 /* open the first image with VDOPEN_FLAGS_INFO because
5220 * it's not necessarily the base one */
5221 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5222 (*it)->m->strLocationFull.c_str(),
5223 it == chain->begin() ?
5224 VD_OPEN_FLAGS_INFO : 0,
5225 (*it)->m->vdDiskIfaces);
5226 if (RT_FAILURE(vrc))
5227 throw vrc;
5228#if 0
5229 LogFlow(("*** MERGE disk = %s\n", (*it)->m->strLocationFull.raw()));
5230#endif
5231 }
5232
5233 /* needed for vdProgressCallback */
5234 that->m->vdProgress = task->progress;
5235
5236 unsigned start = chain->isForward() ?
5237 0 : (unsigned)chain->size() - 1;
5238 unsigned end = chain->isForward() ?
5239 (unsigned)chain->size() - 1 : 0;
5240#if 0
5241 LogFlow(("*** MERGE from %d to %d\n", start, end));
5242#endif
5243 vrc = VDMerge(hdd, start, end, that->m->vdDiskIfaces);
5244
5245 that->m->vdProgress = NULL;
5246
5247 if (RT_FAILURE(vrc))
5248 throw vrc;
5249
5250 /* update parent UUIDs */
5251 /// @todo VDMerge should be taught to do so, including the
5252 /// multiple children case
5253 if (chain->isForward())
5254 {
5255 /* target's UUID needs to be updated (note that target
5256 * is the only image in the container on success) */
5257 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5258 if (RT_FAILURE(vrc))
5259 throw vrc;
5260 }
5261 else
5262 {
5263 /* we need to update UUIDs of all source's children
5264 * which cannot be part of the container at once so
5265 * add each one in there individually */
5266 if (chain->children().size() > 0)
5267 {
5268 for (List::const_iterator it = chain->children().begin();
5269 it != chain->children().end(); ++ it)
5270 {
5271 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5272 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5273 (*it)->m->strLocationFull.c_str(),
5274 VD_OPEN_FLAGS_INFO,
5275 (*it)->m->vdDiskIfaces);
5276 if (RT_FAILURE(vrc))
5277 throw vrc;
5278
5279 vrc = VDSetParentUuid(hdd, 1,
5280 chain->target()->m->id);
5281 if (RT_FAILURE(vrc))
5282 throw vrc;
5283
5284 vrc = VDClose(hdd, false /* fDelete */);
5285 if (RT_FAILURE(vrc))
5286 throw vrc;
5287 }
5288 }
5289 }
5290 }
5291 catch (HRESULT aRC) { rc = aRC; }
5292 catch (int aVRC)
5293 {
5294 throw setError(E_FAIL,
5295 tr("Could not merge the hard disk '%s' to '%s'%s"),
5296 chain->source()->m->strLocationFull.raw(),
5297 chain->target()->m->strLocationFull.raw(),
5298 that->vdError(aVRC).raw());
5299 }
5300
5301 VDDestroy(hdd);
5302 }
5303 catch (HRESULT aRC) { rc = aRC; }
5304
5305 HRESULT rc2;
5306
5307 bool saveSettingsFailed = false;
5308
5309 if (SUCCEEDED(rc))
5310 {
5311 /* all hard disks but the target were successfully deleted by
5312 * VDMerge; reparent the last one and uninitialize deleted */
5313
5314 /* we set mParent & children() (note that thatLock is released
5315 * here), but lock VirtualBox first to follow the rule */
5316 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5317 that->treeLock());
5318
5319 Medium *source = chain->source();
5320 Medium *target = chain->target();
5321
5322 if (chain->isForward())
5323 {
5324 /* first, unregister the target since it may become a base
5325 * hard disk which needs re-registration */
5326 rc2 = target->mVirtualBox->
5327 unregisterHardDisk(target, false /* aSaveSettings */);
5328 AssertComRC(rc2);
5329
5330 /* then, reparent it and disconnect the deleted branch at
5331 * both ends (chain->parent() is source's parent) */
5332 target->mParent->removeDependentChild(target);
5333 target->mParent = chain->parent();
5334 if (!target->mParent.isNull())
5335 {
5336 target->mParent->addDependentChild(target);
5337 target->mParent->removeDependentChild(source);
5338 source->mParent.setNull();
5339 }
5340 else
5341 {
5342 target->mVirtualBox->addDependentChild(target);
5343 target->mVirtualBox->removeDependentChild(source);
5344 }
5345
5346 /* then, register again */
5347 rc2 = target->mVirtualBox->
5348 registerHardDisk(target, false /* aSaveSettings */);
5349 AssertComRC(rc2);
5350 }
5351 else
5352 {
5353 Assert(target->children().size() == 1);
5354 Medium *targetChild = target->children().front();
5355
5356 /* disconnect the deleted branch at the elder end */
5357 target->removeDependentChild(targetChild);
5358 targetChild->mParent.setNull();
5359
5360 const List &children = chain->children();
5361
5362 /* reparent source's chidren and disconnect the deleted
5363 * branch at the younger end m*/
5364 if (children.size() > 0)
5365 {
5366 /* obey {parent,child} lock order */
5367 AutoWriteLock sourceLock(source);
5368
5369 for (List::const_iterator it = children.begin();
5370 it != children.end(); ++ it)
5371 {
5372 AutoWriteLock childLock(*it);
5373
5374 (*it)->mParent = target;
5375 (*it)->mParent->addDependentChild(*it);
5376 source->removeDependentChild(*it);
5377 }
5378 }
5379 }
5380
5381 /* try to save the hard disk registry */
5382 rc = that->mVirtualBox->saveSettings();
5383
5384 if (SUCCEEDED(rc))
5385 {
5386 /* unregister and uninitialize all hard disks in the chain
5387 * but the target */
5388
5389 for (MergeChain::iterator it = chain->begin();
5390 it != chain->end();)
5391 {
5392 if (*it == chain->target())
5393 {
5394 ++ it;
5395 continue;
5396 }
5397
5398 rc2 = (*it)->mVirtualBox->
5399 unregisterHardDisk(*it, false /* aSaveSettings */);
5400 AssertComRC(rc2);
5401
5402 /* now, uninitialize the deleted hard disk (note that
5403 * due to the Deleting state, uninit() will not touch
5404 * the parent-child relationship so we need to
5405 * uninitialize each disk individually) */
5406
5407 /* note that the operation initiator hard disk (which is
5408 * normally also the source hard disk) is a special case
5409 * -- there is one more caller added by Task to it which
5410 * we must release. Also, if we are in sync mode, the
5411 * caller may still hold an AutoCaller instance for it
5412 * and therefore we cannot uninit() it (it's therefore
5413 * the caller's responsibility) */
5414 if (*it == that)
5415 task->autoCaller.release();
5416
5417 /* release the caller added by MergeChain before
5418 * uninit() */
5419 (*it)->releaseCaller();
5420
5421 if (isAsync || *it != that)
5422 (*it)->uninit();
5423
5424 /* delete (to prevent uninitialization in MergeChain
5425 * dtor) and advance to the next item */
5426 it = chain->erase(it);
5427 }
5428
5429 /* Note that states of all other hard disks (target, parent,
5430 * children) will be restored by the MergeChain dtor */
5431 }
5432 else
5433 {
5434 /* too bad if we fail, but we'll need to rollback everything
5435 * we did above to at least keep the HD tree in sync with
5436 * the current registry on disk */
5437
5438 saveSettingsFailed = true;
5439
5440 /// @todo NEWMEDIA implement a proper undo
5441
5442 AssertFailed();
5443 }
5444 }
5445
5446 if (FAILED(rc))
5447 {
5448 /* Here we come if either VDMerge() failed (in which case we
5449 * assume that it tried to do everything to make a further
5450 * retry possible -- e.g. not deleted intermediate hard disks
5451 * and so on) or VirtualBox::saveSettings() failed (where we
5452 * should have the original tree but with intermediate storage
5453 * units deleted by VDMerge()). We have to only restore states
5454 * (through the MergeChain dtor) unless we are run synchronously
5455 * in which case it's the responsibility of the caller as stated
5456 * in the mergeTo() docs. The latter also implies that we
5457 * don't own the merge chain, so release it in this case. */
5458
5459 if (!isAsync)
5460 task->d.chain.release();
5461
5462 NOREF(saveSettingsFailed);
5463 }
5464
5465 break;
5466 }
5467
5468 ////////////////////////////////////////////////////////////////////////
5469
5470 case Task::Clone:
5471 {
5472 ComObjPtr<Medium> &target = task->d.target;
5473 ComObjPtr<Medium> &parent = task->d.parentDisk;
5474
5475 /* Lock all in {parent,child} order. The lock is also used as a
5476 * signal from the task initiator (which releases it only after
5477 * RTThreadCreate()) that we can start the job. */
5478 AutoMultiWriteLock3 thatLock(that, target, parent);
5479
5480 ImageChain *srcChain = task->d.source.get();
5481 ImageChain *parentChain = task->d.parent.get();
5482
5483 uint64_t size = 0, logicalSize = 0;
5484
5485 /* The object may request a specific UUID (through a special form of
5486 * the setLocation() argument). Otherwise we have to generate it */
5487 Guid targetId = target->m->id;
5488 bool generateUuid = targetId.isEmpty();
5489 if (generateUuid)
5490 {
5491 targetId.create();
5492 /* VirtualBox::registerHardDisk() will need UUID */
5493 unconst(target->m->id) = targetId;
5494 }
5495
5496 try
5497 {
5498 PVBOXHDD hdd;
5499 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5500 ComAssertRCThrow(vrc, E_FAIL);
5501
5502 try
5503 {
5504 /* Open all hard disk images in the source chain. */
5505 for (List::const_iterator it = srcChain->begin();
5506 it != srcChain->end(); ++ it)
5507 {
5508 /* sanity check */
5509 Assert((*it)->m->state == MediumState_LockedRead);
5510
5511 /** Open all images in read-only mode. */
5512 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5513 (*it)->m->strLocationFull.c_str(),
5514 VD_OPEN_FLAGS_READONLY,
5515 (*it)->m->vdDiskIfaces);
5516 if (RT_FAILURE(vrc))
5517 {
5518 throw setError(E_FAIL,
5519 tr("Could not open the hard disk storage unit '%s'%s"),
5520 (*it)->m->strLocationFull.raw(),
5521 that->vdError(vrc).raw());
5522 }
5523 }
5524
5525 /* unlock before the potentially lengthy operation */
5526 thatLock.leave();
5527
5528 Utf8Str targetFormat(target->m->strFormat);
5529 Utf8Str targetLocation(target->m->strLocationFull);
5530
5531 Assert( target->m->state == MediumState_Creating
5532 || target->m->state == MediumState_LockedWrite);
5533 Assert(that->m->state == MediumState_LockedRead);
5534 Assert(parent.isNull() || parent->m->state == MediumState_LockedRead);
5535
5536 /* ensure the target directory exists */
5537 rc = VirtualBox::ensureFilePathExists(targetLocation);
5538 CheckComRCThrowRC(rc);
5539
5540 /* needed for vdProgressCallback */
5541 that->m->vdProgress = task->progress;
5542
5543 PVBOXHDD targetHdd;
5544 int vrc = VDCreate(that->m->vdDiskIfaces, &targetHdd);
5545 ComAssertRCThrow(vrc, E_FAIL);
5546
5547 try
5548 {
5549 /* Open all hard disk images in the parent chain. */
5550 for (List::const_iterator it = parentChain->begin();
5551 it != parentChain->end(); ++ it)
5552 {
5553 /* sanity check */
5554 Assert( (*it)->m->state == MediumState_LockedRead
5555 || (*it)->m->state == MediumState_LockedWrite);
5556
5557 /* Open all images in appropriate mode. */
5558 vrc = VDOpen(targetHdd, (*it)->m->strFormat.c_str(),
5559 (*it)->m->strLocationFull.c_str(),
5560 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5561 (*it)->m->vdDiskIfaces);
5562 if (RT_FAILURE(vrc))
5563 {
5564 throw setError(E_FAIL,
5565 tr("Could not open the hard disk storage unit '%s'%s"),
5566 (*it)->m->strLocationFull.raw(),
5567 that->vdError(vrc).raw());
5568 }
5569 }
5570
5571 vrc = VDCopy(hdd, VD_LAST_IMAGE, targetHdd,
5572 targetFormat.c_str(),
5573 target->m->state == MediumState_Creating ? targetLocation.raw() : (char *)NULL,
5574 false, 0,
5575 task->d.variant, targetId.raw(), NULL,
5576 target->m->vdDiskIfaces,
5577 that->m->vdDiskIfaces);
5578
5579 that->m->vdProgress = NULL;
5580
5581 if (RT_FAILURE(vrc))
5582 {
5583 throw setError(E_FAIL,
5584 tr("Could not create the clone hard disk '%s'%s"),
5585 targetLocation.raw(), that->vdError(vrc).raw());
5586 }
5587 size = VDGetFileSize(targetHdd, 0);
5588 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5589 }
5590 catch (HRESULT aRC) { rc = aRC; }
5591
5592 VDDestroy(targetHdd);
5593 }
5594 catch (HRESULT aRC) { rc = aRC; }
5595
5596 VDDestroy(hdd);
5597 }
5598 catch (HRESULT aRC) { rc = aRC; }
5599
5600 /* Only do the parent changes for newly created images. */
5601 if (target->m->state == MediumState_Creating)
5602 {
5603 if (SUCCEEDED(rc))
5604 {
5605 /* we set mParent & children() (note that thatLock is released
5606 * here), but lock VirtualBox first to follow the rule */
5607 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5608 that->treeLock());
5609
5610 Assert(target->mParent.isNull());
5611
5612 if (parent)
5613 {
5614 /* associate the clone with the parent and deassociate
5615 * from VirtualBox */
5616 target->mParent = parent;
5617 parent->addDependentChild(target);
5618 target->mVirtualBox->removeDependentChild(target);
5619
5620 /* register with mVirtualBox as the last step and move to
5621 * Created state only on success (leaving an orphan file is
5622 * better than breaking media registry consistency) */
5623 rc = parent->mVirtualBox->registerHardDisk(target);
5624
5625 if (FAILED(rc))
5626 {
5627 /* break parent association on failure to register */
5628 target->mVirtualBox->addDependentChild(target);
5629 parent->removeDependentChild(target);
5630 target->mParent.setNull();
5631 }
5632 }
5633 else
5634 {
5635 /* just register */
5636 rc = that->mVirtualBox->registerHardDisk(target);
5637 }
5638 }
5639 }
5640
5641 thatLock.maybeEnter();
5642
5643 if (target->m->state == MediumState_Creating)
5644 {
5645 if (SUCCEEDED(rc))
5646 {
5647 target->m->state = MediumState_Created;
5648
5649 target->m->size = size;
5650 target->m->logicalSize = logicalSize;
5651 }
5652 else
5653 {
5654 /* back to NotCreated on failure */
5655 target->m->state = MediumState_NotCreated;
5656
5657 /* reset UUID to prevent it from being reused next time */
5658 if (generateUuid)
5659 unconst(target->m->id).clear();
5660 }
5661 }
5662
5663 /* Everything is explicitly unlocked when the task exits,
5664 * as the task destruction also destroys the source chain. */
5665
5666 /* Make sure the source chain is released early. It could happen
5667 * that we get a deadlock in Appliance::Import when Medium::Close
5668 * is called & the source chain is released at the same time. */
5669 task->d.source.reset();
5670 break;
5671 }
5672
5673 ////////////////////////////////////////////////////////////////////////
5674
5675 case Task::Delete:
5676 {
5677 /* The lock is also used as a signal from the task initiator (which
5678 * releases it only after RTThreadCreate()) that we can start the job */
5679 AutoWriteLock thatLock(that);
5680
5681 try
5682 {
5683 PVBOXHDD hdd;
5684 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5685 ComAssertRCThrow(vrc, E_FAIL);
5686
5687 Utf8Str format(that->m->strFormat);
5688 Utf8Str location(that->m->strLocationFull);
5689
5690 /* unlock before the potentially lengthy operation */
5691 Assert(that->m->state == MediumState_Deleting);
5692 thatLock.leave();
5693
5694 try
5695 {
5696 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5697 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5698 that->m->vdDiskIfaces);
5699 if (RT_SUCCESS(vrc))
5700 vrc = VDClose(hdd, true /* fDelete */);
5701
5702 if (RT_FAILURE(vrc))
5703 {
5704 throw setError(E_FAIL,
5705 tr("Could not delete the hard disk storage unit '%s'%s"),
5706 location.raw(), that->vdError(vrc).raw());
5707 }
5708
5709 }
5710 catch (HRESULT aRC) { rc = aRC; }
5711
5712 VDDestroy(hdd);
5713 }
5714 catch (HRESULT aRC) { rc = aRC; }
5715
5716 thatLock.maybeEnter();
5717
5718 /* go to the NotCreated state even on failure since the storage
5719 * may have been already partially deleted and cannot be used any
5720 * more. One will be able to manually re-open the storage if really
5721 * needed to re-register it. */
5722 that->m->state = MediumState_NotCreated;
5723
5724 /* Reset UUID to prevent Create* from reusing it again */
5725 unconst(that->m->id).clear();
5726
5727 break;
5728 }
5729
5730 case Task::Reset:
5731 {
5732 /* The lock is also used as a signal from the task initiator (which
5733 * releases it only after RTThreadCreate()) that we can start the job */
5734 AutoWriteLock thatLock(that);
5735
5736 /// @todo Below we use a pair of delete/create operations to reset
5737 /// the diff contents but the most efficient way will of course be
5738 /// to add a VDResetDiff() API call
5739
5740 uint64_t size = 0, logicalSize = 0;
5741
5742 try
5743 {
5744 PVBOXHDD hdd;
5745 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5746 ComAssertRCThrow(vrc, E_FAIL);
5747
5748 Guid id = that->m->id;
5749 Utf8Str format(that->m->strFormat);
5750 Utf8Str location(that->m->strLocationFull);
5751
5752 Guid parentId = that->mParent->m->id;
5753 Utf8Str parentFormat(that->mParent->m->strFormat);
5754 Utf8Str parentLocation(that->mParent->m->strLocationFull);
5755
5756 Assert(that->m->state == MediumState_LockedWrite);
5757
5758 /* unlock before the potentially lengthy operation */
5759 thatLock.leave();
5760
5761 try
5762 {
5763 /* first, delete the storage unit */
5764 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5765 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5766 that->m->vdDiskIfaces);
5767 if (RT_SUCCESS(vrc))
5768 vrc = VDClose(hdd, true /* fDelete */);
5769
5770 if (RT_FAILURE(vrc))
5771 {
5772 throw setError(E_FAIL,
5773 tr("Could not delete the hard disk storage unit '%s'%s"),
5774 location.raw(), that->vdError(vrc).raw());
5775 }
5776
5777 /* next, create it again */
5778 vrc = VDOpen(hdd, parentFormat.c_str(), parentLocation.c_str(),
5779 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5780 that->m->vdDiskIfaces);
5781 if (RT_FAILURE(vrc))
5782 {
5783 throw setError(E_FAIL,
5784 tr("Could not open the hard disk storage unit '%s'%s"),
5785 parentLocation.raw(), that->vdError(vrc).raw());
5786 }
5787
5788 /* needed for vdProgressCallback */
5789 that->m->vdProgress = task->progress;
5790
5791 vrc = VDCreateDiff(hdd, format.c_str(), location.c_str(),
5792 /// @todo use the same image variant as before
5793 VD_IMAGE_FLAGS_NONE,
5794 NULL, id.raw(),
5795 parentId.raw(),
5796 VD_OPEN_FLAGS_NORMAL,
5797 that->m->vdDiskIfaces,
5798 that->m->vdDiskIfaces);
5799
5800 that->m->vdProgress = NULL;
5801
5802 if (RT_FAILURE(vrc))
5803 {
5804 throw setError(E_FAIL,
5805 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5806 location.raw(), that->vdError(vrc).raw());
5807 }
5808
5809 size = VDGetFileSize(hdd, 1);
5810 logicalSize = VDGetSize(hdd, 1) / _1M;
5811 }
5812 catch (HRESULT aRC) { rc = aRC; }
5813
5814 VDDestroy(hdd);
5815 }
5816 catch (HRESULT aRC) { rc = aRC; }
5817
5818 thatLock.enter();
5819
5820 that->m->size = size;
5821 that->m->logicalSize = logicalSize;
5822
5823 if (isAsync)
5824 {
5825 /* unlock ourselves when done */
5826 HRESULT rc2 = that->UnlockWrite(NULL);
5827 AssertComRC(rc2);
5828 }
5829
5830 /* Note that in sync mode, it's the caller's responsibility to
5831 * unlock the hard disk */
5832
5833 break;
5834 }
5835
5836 ////////////////////////////////////////////////////////////////////////
5837
5838 case Task::Compact:
5839 {
5840 /* Lock all in {parent,child} order. The lock is also used as a
5841 * signal from the task initiator (which releases it only after
5842 * RTThreadCreate()) that we can start the job. */
5843 AutoWriteLock thatLock(that);
5844
5845 ImageChain *imgChain = task->d.images.get();
5846
5847 try
5848 {
5849 PVBOXHDD hdd;
5850 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5851 ComAssertRCThrow(vrc, E_FAIL);
5852
5853 try
5854 {
5855 /* Open all hard disk images in the chain. */
5856 List::const_iterator last = imgChain->end();
5857 last--;
5858 for (List::const_iterator it = imgChain->begin();
5859 it != imgChain->end(); ++ it)
5860 {
5861 /* sanity check */
5862 if (it == last)
5863 Assert((*it)->m->state == MediumState_LockedWrite);
5864 else
5865 Assert((*it)->m->state == MediumState_LockedRead);
5866
5867 /** Open all images but last in read-only mode. */
5868 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5869 (*it)->m->strLocationFull.c_str(),
5870 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5871 (*it)->m->vdDiskIfaces);
5872 if (RT_FAILURE(vrc))
5873 {
5874 throw setError(E_FAIL,
5875 tr("Could not open the hard disk storage unit '%s'%s"),
5876 (*it)->m->strLocationFull.raw(),
5877 that->vdError(vrc).raw());
5878 }
5879 }
5880
5881 /* unlock before the potentially lengthy operation */
5882 thatLock.leave();
5883
5884 Assert(that->m->state == MediumState_LockedWrite);
5885
5886 /* needed for vdProgressCallback */
5887 that->m->vdProgress = task->progress;
5888
5889 vrc = VDCompact(hdd, VD_LAST_IMAGE, that->m->vdDiskIfaces);
5890
5891 that->m->vdProgress = NULL;
5892
5893 if (RT_FAILURE(vrc))
5894 {
5895 if (vrc == VERR_NOT_SUPPORTED)
5896 throw setError(VBOX_E_NOT_SUPPORTED,
5897 tr("Compacting is not yet supported for hard disk '%s'"),
5898 that->m->strLocationFull.raw());
5899 else if (vrc == VERR_NOT_IMPLEMENTED)
5900 throw setError(E_NOTIMPL,
5901 tr("Compacting is not implemented, hard disk '%s'"),
5902 that->m->strLocationFull.raw());
5903 else
5904 throw setError(E_FAIL,
5905 tr("Could not compact hard disk '%s'%s"),
5906 that->m->strLocationFull.raw(),
5907 that->vdError(vrc).raw());
5908 }
5909 }
5910 catch (HRESULT aRC) { rc = aRC; }
5911
5912 VDDestroy(hdd);
5913 }
5914 catch (HRESULT aRC) { rc = aRC; }
5915
5916 /* Everything is explicitly unlocked when the task exits,
5917 * as the task destruction also destroys the image chain. */
5918
5919 break;
5920 }
5921
5922 default:
5923 AssertFailedReturn(VERR_GENERAL_FAILURE);
5924 }
5925
5926 /* complete the progress if run asynchronously */
5927 if (isAsync)
5928 {
5929 if (!task->progress.isNull())
5930 task->progress->notifyComplete(rc);
5931 }
5932 else
5933 {
5934 task->rc = rc;
5935 }
5936
5937 LogFlowFunc(("rc=%Rhrc\n", rc));
5938 LogFlowFuncLeave();
5939
5940 return VINF_SUCCESS;
5941
5942 /// @todo ugly hack, fix ComAssert... later
5943 #undef setError
5944}
5945
5946/* 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