VirtualBox

source: vbox/trunk/src/VBox/Main/HardDisk2Impl.cpp@ 14143

Last change on this file since 14143 was 14143, checked in by vboxsync, 16 years ago

Main: Improved hard disk tree lock usage to avoid possible deadlocks when walking the hard disk tree upwards (from children to parents).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 108.2 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2008 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22#include "HardDisk2Impl.h"
23
24#include "VirtualBoxImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27
28#include "Logging.h"
29
30#include <VBox/com/array.h>
31#include <VBox/com/SupportErrorInfo.h>
32
33#include <VBox/err.h>
34
35#include <iprt/param.h>
36#include <iprt/path.h>
37#include <iprt/file.h>
38
39#include <list>
40#include <memory>
41
42////////////////////////////////////////////////////////////////////////////////
43// Globals
44////////////////////////////////////////////////////////////////////////////////
45
46/**
47 * Asynchronous task thread parameter bucket.
48 *
49 * Note that instances of this class must be created using new() because the
50 * task thread function will delete them when the task is complete!
51 *
52 * @note The constructor of this class adds a caller on the managed HardDisk2
53 * object which is automatically released upon destruction.
54 */
55struct HardDisk2::Task : public com::SupportErrorInfoBase
56{
57 enum Operation { CreateDynamic, CreateFixed, CreateDiff, Merge, Delete };
58
59 HardDisk2 *that;
60 VirtualBoxBaseProto::AutoCaller autoCaller;
61
62 ComObjPtr <Progress> progress;
63 Operation operation;
64
65 /** Where to save the result when executed using #runNow(). */
66 HRESULT rc;
67
68 Task (HardDisk2 *aThat, Progress *aProgress, Operation aOperation)
69 : that (aThat), autoCaller (aThat)
70 , progress (aProgress)
71 , operation (aOperation)
72 , rc (S_OK) {}
73
74 ~Task();
75
76 void setData (HardDisk2 *aTarget)
77 {
78 d.target = aTarget;
79 HRESULT rc = d.target->addCaller();
80 AssertComRC (rc);
81 }
82
83 void setData (MergeChain *aChain)
84 {
85 AssertReturnVoid (aChain != NULL);
86 d.chain.reset (aChain);
87 }
88
89 HRESULT startThread();
90 HRESULT runNow();
91
92 struct Data
93 {
94 Data() : size (0) {}
95
96 /* CreateDynamic, CreateStatic */
97
98 uint64_t size;
99
100 /* CreateDiff */
101
102 ComObjPtr <HardDisk2> target;
103
104 /* Merge */
105
106 /** Hard disks to merge, in {parent,child} order */
107 std::auto_ptr <MergeChain> chain;
108 }
109 d;
110
111protected:
112
113 // SupportErrorInfoBase interface
114 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk2); }
115 const char *componentName() const { return HardDisk2::ComponentName(); }
116};
117
118HardDisk2::Task::~Task()
119{
120 /* remove callers added by setData() */
121 if (!d.target.isNull())
122 d.target->releaseCaller();
123}
124
125/**
126 * Starts a new thread driven by the HardDisk2::taskThread() function and passes
127 * this Task instance as an argument.
128 *
129 * Note that if this method returns success, this Task object becomes an ownee
130 * of the started thread and will be automatically deleted when the thread
131 * terminates.
132 *
133 * @note When the task is executed by this method, IProgress::notifyComplete()
134 * is automatically called for the progress object associated with this
135 * task when the task is finished to signal the operation completion for
136 * other threads asynchronously waiting for it.
137 */
138HRESULT HardDisk2::Task::startThread()
139{
140 int vrc = RTThreadCreate (NULL, HardDisk2::taskThread, this,
141 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
142 "HardDisk::Task");
143 ComAssertMsgRCRet (vrc,
144 ("Could not create HardDisk::Task thread (%Rrc)\n", vrc), E_FAIL);
145
146 return S_OK;
147}
148
149/**
150 * Runs HardDisk2::taskThread() by passing it this Task instance as an argument
151 * on the current thread instead of creating a new one.
152 *
153 * This call implies that it is made on another temporary thread created for
154 * some asynchronous task. Avoid calling it from a normal thread since the task
155 * operatinos are potentially lengthy and will block the calling thread in this
156 * case.
157 *
158 * Note that this Task object will be deleted by taskThread() when this method
159 * returns!
160 *
161 * @note When the task is executed by this method, IProgress::notifyComplete()
162 * is not called for the progress object associated with this task when
163 * the task is finished. Instead, the result of the operation is returned
164 * by this method directly and it's the caller's responsibility to
165 * complete the progress object in this case.
166 */
167HRESULT HardDisk2::Task::runNow()
168{
169 HardDisk2::taskThread (NIL_RTTHREAD, this);
170
171 return rc;
172}
173
174////////////////////////////////////////////////////////////////////////////////
175
176/**
177 * Helper class for merge operations.
178 *
179 * @note It is assumed that when modifying methods of this class are called,
180 * HardDisk2::treeLock() is held in read mode.
181 */
182class HardDisk2::MergeChain : public HardDisk2::List,
183 public com::SupportErrorInfoBase
184{
185public:
186
187 MergeChain (bool aForward, bool aIgnoreAttachments)
188 : mForward (aForward)
189 , mIgnoreAttachments (aIgnoreAttachments) {}
190
191 ~MergeChain()
192 {
193 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
194 {
195 HRESULT rc = (*it)->UnlockWrite (NULL);
196 AssertComRC (rc);
197
198 (*it)->releaseCaller();
199 }
200
201 for (iterator it = begin(); it != end(); ++ it)
202 {
203 AutoWriteLock alock (*it);
204 Assert ((*it)->m.state == MediaState_LockedWrite ||
205 (*it)->m.state == MediaState_Deleting);
206 if ((*it)->m.state == MediaState_LockedWrite)
207 (*it)->UnlockWrite (NULL);
208 else
209 (*it)->m.state = MediaState_Created;
210
211 (*it)->releaseCaller();
212 }
213
214 if (!mParent.isNull())
215 mParent->releaseCaller();
216 }
217
218 HRESULT addSource (HardDisk2 *aHardDisk)
219 {
220 HRESULT rc = aHardDisk->addCaller();
221 CheckComRCReturnRC (rc);
222
223 AutoWriteLock alock (aHardDisk);
224
225 if (mForward)
226 {
227 rc = checkChildrenAndAttachmentsAndImmutable (aHardDisk);
228 if (FAILED (rc))
229 {
230 aHardDisk->releaseCaller();
231 return rc;
232 }
233 }
234
235 /* go to Deleting */
236 switch (aHardDisk->m.state)
237 {
238 case MediaState_Created:
239 aHardDisk->m.state = MediaState_Deleting;
240 break;
241 default:
242 aHardDisk->releaseCaller();
243 return aHardDisk->setStateError();
244 }
245
246 push_front (aHardDisk);
247
248 if (mForward)
249 {
250 /* we will need parent to reparent target */
251 if (!aHardDisk->mParent.isNull())
252 {
253 rc = aHardDisk->mParent->addCaller();
254 CheckComRCReturnRC (rc);
255
256 mParent = aHardDisk->mParent;
257 }
258 }
259 else
260 {
261 /* we will need to reparent children */
262 for (List::const_iterator it = aHardDisk->children().begin();
263 it != aHardDisk->children().end(); ++ it)
264 {
265 rc = (*it)->addCaller();
266 CheckComRCReturnRC (rc);
267
268 rc = (*it)->LockWrite (NULL);
269 if (FAILED (rc))
270 {
271 (*it)->releaseCaller();
272 return rc;
273 }
274
275 mChildren.push_back (*it);
276 }
277 }
278
279 return S_OK;
280 }
281
282 HRESULT addTarget (HardDisk2 *aHardDisk)
283 {
284 HRESULT rc = aHardDisk->addCaller();
285 CheckComRCReturnRC (rc);
286
287 AutoWriteLock alock (aHardDisk);
288
289 if (!mForward)
290 {
291 rc = checkChildrenAndImmutable (aHardDisk);
292 if (FAILED (rc))
293 {
294 aHardDisk->releaseCaller();
295 return rc;
296 }
297 }
298
299 /* go to LockedWrite */
300 rc = aHardDisk->LockWrite (NULL);
301 if (FAILED (rc))
302 {
303 aHardDisk->releaseCaller();
304 return rc;
305 }
306
307 push_front (aHardDisk);
308
309 return S_OK;
310 }
311
312 HRESULT addIntermediate (HardDisk2 *aHardDisk)
313 {
314 HRESULT rc = aHardDisk->addCaller();
315 CheckComRCReturnRC (rc);
316
317 AutoWriteLock alock (aHardDisk);
318
319 rc = checkChildrenAndAttachments (aHardDisk);
320 if (FAILED (rc))
321 {
322 aHardDisk->releaseCaller();
323 return rc;
324 }
325
326 /* go to Deleting */
327 switch (aHardDisk->m.state)
328 {
329 case MediaState_Created:
330 aHardDisk->m.state = MediaState_Deleting;
331 break;
332 default:
333 aHardDisk->releaseCaller();
334 return aHardDisk->setStateError();
335 }
336
337 push_front (aHardDisk);
338
339 return S_OK;
340 }
341
342 bool isForward() const { return mForward; }
343 HardDisk2 *parent() const { return mParent; }
344 const List &children() const { return mChildren; }
345
346 HardDisk2 *source() const
347 { AssertReturn (size() > 0, NULL); return mForward ? front() : back(); }
348
349 HardDisk2 *target() const
350 { AssertReturn (size() > 0, NULL); return mForward ? back() : front(); }
351
352protected:
353
354 // SupportErrorInfoBase interface
355 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk2); }
356 const char *componentName() const { return HardDisk2::ComponentName(); }
357
358private:
359
360 HRESULT check (HardDisk2 *aHardDisk, bool aChildren, bool aAttachments,
361 bool aImmutable)
362 {
363 if (aChildren)
364 {
365 /* not going to multi-merge as it's too expensive */
366 if (aHardDisk->children().size() > 1)
367 {
368 return setError (E_FAIL,
369 tr ("Hard disk '%ls' involved in the merge operation "
370 "has more than one child hard disk (%d)"),
371 aHardDisk->m.locationFull.raw(),
372 aHardDisk->children().size());
373 }
374 }
375
376 if (aAttachments && !mIgnoreAttachments)
377 {
378 if (aHardDisk->m.backRefs.size() != 0)
379 return setError (E_FAIL,
380 tr ("Hard disk '%ls' is attached to %d virtual machines"),
381 aHardDisk->m.locationFull.raw(),
382 aHardDisk->m.backRefs.size());
383 }
384
385 if (aImmutable)
386 {
387 if (aHardDisk->mm.type == HardDiskType_Immutable)
388 return setError (E_FAIL,
389 tr ("Hard disk '%ls' is immutable"),
390 aHardDisk->m.locationFull.raw());
391 }
392
393 return S_OK;
394 }
395
396 HRESULT checkChildren (HardDisk2 *aHardDisk)
397 { return check (aHardDisk, true, false, false); }
398
399 HRESULT checkChildrenAndImmutable (HardDisk2 *aHardDisk)
400 { return check (aHardDisk, true, false, true); }
401
402 HRESULT checkChildrenAndAttachments (HardDisk2 *aHardDisk)
403 { return check (aHardDisk, true, true, false); }
404
405 HRESULT checkChildrenAndAttachmentsAndImmutable (HardDisk2 *aHardDisk)
406 { return check (aHardDisk, true, true, true); }
407
408 /** true if forward merge, false if backward */
409 bool mForward : 1;
410 /** true to not perform attachment checks */
411 bool mIgnoreAttachments : 1;
412
413 /** Parent of the source when forward merge (if any) */
414 ComObjPtr <HardDisk2> mParent;
415 /** Children of the source when backward merge (if any) */
416 List mChildren;
417};
418
419////////////////////////////////////////////////////////////////////////////////
420// HardDisk2 class
421////////////////////////////////////////////////////////////////////////////////
422
423// constructor / destructor
424////////////////////////////////////////////////////////////////////////////////
425
426DEFINE_EMPTY_CTOR_DTOR (HardDisk2)
427
428HRESULT HardDisk2::FinalConstruct()
429{
430 /* Initialize the callbacks of the VD error interface */
431 mm.vdIfCallsError.cbSize = sizeof (VDINTERFACEERROR);
432 mm.vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
433 mm.vdIfCallsError.pfnError = vdErrorCall;
434
435 /* Initialize the callbacks of the VD progress interface */
436 mm.vdIfCallsProgress.cbSize = sizeof (VDINTERFACEPROGRESS);
437 mm.vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
438 mm.vdIfCallsProgress.pfnProgress = vdProgressCall;
439
440 /* Initialize the per-disk interface chain */
441 int vrc;
442 vrc = VDInterfaceAdd (&mm.vdIfError,
443 "HardDisk2::vdInterfaceError",
444 VDINTERFACETYPE_ERROR,
445 &mm.vdIfCallsError, this, &mm.vdDiskIfaces);
446 AssertRCReturn (vrc, E_FAIL);
447 vrc = VDInterfaceAdd (&mm.vdIfProgress,
448 "HardDisk2::vdInterfaceProgress",
449 VDINTERFACETYPE_PROGRESS,
450 &mm.vdIfCallsProgress, this, &mm.vdDiskIfaces);
451 AssertRCReturn (vrc, E_FAIL);
452
453 return S_OK;
454}
455
456void HardDisk2::FinalRelease()
457{
458 uninit();
459}
460
461// public initializer/uninitializer for internal purposes only
462////////////////////////////////////////////////////////////////////////////////
463
464/**
465 * Initializes the hard disk object without creating or opening an associated
466 * storage unit.
467 *
468 * @param aVirtualBox VirtualBox object.
469 * @param aLocaiton Storage unit location.
470 */
471HRESULT HardDisk2::init (VirtualBox *aVirtualBox, const BSTR aFormat,
472 const BSTR aLocation)
473{
474 AssertReturn (aVirtualBox, E_INVALIDARG);
475 AssertReturn (aLocation, E_INVALIDARG);
476
477 /* Enclose the state transition NotReady->InInit->Ready */
478 AutoInitSpan autoInitSpan (this);
479 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
480
481 HRESULT rc = S_OK;
482
483 /* share VirtualBox weakly (parent remains NULL so far) */
484 unconst (mVirtualBox) = aVirtualBox;
485
486 /* register with VirtualBox early, since uninit() will
487 * unconditionally unregister on failure */
488 aVirtualBox->addDependentChild (this);
489
490 /* no storage yet */
491 m.state = MediaState_NotCreated;
492
493 rc = setLocation (aLocation);
494 CheckComRCReturnRC (rc);
495
496 /* No storage unit is created yet, no need to queryInfo() */
497
498 /// @todo NEWMEDIA check the format ID is valid
499 unconst (mm.format) = aFormat;
500
501 /* Confirm a successful initialization when it's the case */
502 if (SUCCEEDED (rc))
503 autoInitSpan.setSucceeded();
504
505 return rc;
506}
507
508/**
509 * Initializes the hard disk object by opening the storage unit at the specified
510 * location.
511 *
512 * Note that the UUID, format and the parent of this hard disk will be
513 * determined when reading the hard disk storage unit. If the detected parent is
514 * not known to VirtualBox, then this method will fail.
515 *
516 * @param aVirtualBox VirtualBox object.
517 * @param aLocaiton Storage unit location.
518 */
519HRESULT HardDisk2::init (VirtualBox *aVirtualBox, const BSTR aLocation)
520{
521 AssertReturn (aVirtualBox, E_INVALIDARG);
522 AssertReturn (aLocation, E_INVALIDARG);
523
524 /* Enclose the state transition NotReady->InInit->Ready */
525 AutoInitSpan autoInitSpan (this);
526 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
527
528 HRESULT rc = S_OK;
529
530 /* share VirtualBox weakly (parent remains NULL so far) */
531 unconst (mVirtualBox) = aVirtualBox;
532
533 /* register with VirtualBox early, since uninit() will
534 * unconditionally unregister on failure */
535 aVirtualBox->addDependentChild (this);
536
537 /* there must be a storage unit */
538 m.state = MediaState_Created;
539
540 rc = setLocation (aLocation);
541 CheckComRCReturnRC (rc);
542
543 /* get all the information about the medium from the storage unit */
544 rc = queryInfo();
545 if (SUCCEEDED (rc))
546 {
547 /* if the storage unit is not accessible, it's not acceptable for the
548 * newly opened media so convert this into an error */
549 if (m.state == MediaState_Inaccessible)
550 {
551 Assert (!m.lastAccessError.isNull());
552 rc = setError (E_FAIL, Utf8Str (m.lastAccessError));
553 }
554 }
555
556 /* storage format must be detected by queryInfo() if the medium is
557 * accessible */
558 AssertReturn (m.state == MediaState_Inaccessible ||
559 (!m.id.isEmpty() && !mm.format.isNull()),
560 E_FAIL);
561
562 /* Confirm a successful initialization when it's the case */
563 if (SUCCEEDED (rc))
564 autoInitSpan.setSucceeded();
565
566 return rc;
567}
568
569/**
570 * Initializes the hard disk object by loading its data from the given settings
571 * node.
572 *
573 * @param aVirtualBox VirtualBox object.
574 * @param aParent Parent hard disk or NULL for a root hard disk.
575 * @param aNode <HardDisk> settings node.
576 *
577 * @note Locks VirtualBox lock for writing, treeLock() for writing.
578 */
579HRESULT HardDisk2::init (VirtualBox *aVirtualBox, HardDisk2 *aParent,
580 const settings::Key &aNode)
581{
582 using namespace settings;
583
584 AssertReturn (aVirtualBox, E_INVALIDARG);
585
586 /* Enclose the state transition NotReady->InInit->Ready */
587 AutoInitSpan autoInitSpan (this);
588 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
589
590 HRESULT rc = S_OK;
591
592 /* share VirtualBox and parent weakly */
593 unconst (mVirtualBox) = aVirtualBox;
594
595 /* register with VirtualBox/parent early, since uninit() will
596 * unconditionally unregister on failure */
597 if (aParent == NULL)
598 aVirtualBox->addDependentChild (this);
599
600 {
601 /* we set mParent */
602 AutoWriteLock treeLock (this->treeLock());
603
604 mParent = aParent;
605 aParent->addDependentChild (this);
606 }
607
608 /* see below why we don't call queryInfo() (and therefore treat the medium
609 * as inaccessible for now */
610 m.state = MediaState_Inaccessible;
611
612 /* required */
613 unconst (m.id) = aNode.value <Guid> ("uuid");
614 /* required */
615 Bstr location = aNode.stringValue ("location");
616 rc = setLocation (location);
617 CheckComRCReturnRC (rc);
618 /* optional */
619 {
620 settings::Key descNode = aNode.findKey ("Description");
621 if (!descNode.isNull())
622 m.description = descNode.keyStringValue();
623 }
624
625 /* required */
626 unconst (mm.format) = aNode.stringValue ("format");
627 AssertReturn (!mm.format.isNull(), E_FAIL);
628
629 /* only for base hard disks */
630 if (mParent.isNull())
631 {
632 const char *type = aNode.stringValue ("type");
633 if (strcmp (type, "Normal") == 0)
634 mm.type = HardDiskType_Normal;
635 else if (strcmp (type, "Immutable") == 0)
636 mm.type = HardDiskType_Immutable;
637 else if (strcmp (type, "Writethrough") == 0)
638 mm.type = HardDiskType_Writethrough;
639 else
640 AssertFailed();
641 }
642
643 LogFlowThisFunc (("m.location='%ls', mm.format=%ls, m.id={%RTuuid}\n",
644 m.location.raw(), mm.format.raw(), m.id.raw()));
645 LogFlowThisFunc (("m.locationFull='%ls'\n", m.locationFull.raw()));
646
647 /* Don't call queryInfo() for registered media to prevent the calling
648 * thread (i.e. the VirtualBox server startup thread) from an unexpected
649 * freeze but mark it as initially inaccessible instead. The vital UUID,
650 * location and format properties are read from the registry file above; to
651 * get the actual state and the the rest of data, the user will have to call
652 * COMGETTER(State). */
653
654 /* load all children */
655 Key::List hardDisks = aNode.keys ("HardDisk");
656 for (Key::List::const_iterator it = hardDisks.begin();
657 it != hardDisks.end(); ++ it)
658 {
659 ComObjPtr <HardDisk2> hardDisk;
660 hardDisk.createObject();
661 rc = hardDisk->init (aVirtualBox, this, *it);
662 CheckComRCBreakRC (rc);
663
664 rc = mVirtualBox->registerHardDisk2 (hardDisk, false /* aSaveRegistry */);
665 CheckComRCBreakRC (rc);
666 }
667
668 /* Confirm a successful initialization when it's the case */
669 if (SUCCEEDED (rc))
670 autoInitSpan.setSucceeded();
671
672 return rc;
673}
674
675/**
676 * Uninitializes the instance.
677 *
678 * Called either from FinalRelease() or by the parent when it gets destroyed.
679 *
680 * @note All children of this hard disk get uninitialized by calling their
681 * uninit() methods.
682 *
683 * @note Locks treeLock() for writing, VirtualBox for writing.
684 */
685void HardDisk2::uninit()
686{
687 /* Enclose the state transition Ready->InUninit->NotReady */
688 AutoUninitSpan autoUninitSpan (this);
689 if (autoUninitSpan.uninitDone())
690 return;
691
692 if (m.state == MediaState_Deleting)
693 {
694 /* we are being uninitialized after've been deleted by merge.
695 * Reparenting has already been done so don't touch it here (we are
696 * now orphans and remoeDependentChild() will assert) */
697
698 Assert (mParent.isNull());
699 }
700 else
701 {
702 /* we uninit children and reset mParent
703 * and VirtualBox::removeDependentChild() needs a write lock */
704 AutoMultiWriteLock2 alock (mVirtualBox->lockHandle(), this->treeLock());
705
706 uninitDependentChildren();
707
708 if (!mParent.isNull())
709 {
710 mParent->removeDependentChild (this);
711 mParent.setNull();
712 }
713 else
714 mVirtualBox->removeDependentChild (this);
715 }
716
717 unconst (mVirtualBox).setNull();
718}
719
720// IHardDisk2 properties
721////////////////////////////////////////////////////////////////////////////////
722
723STDMETHODIMP HardDisk2::COMGETTER(Format) (BSTR *aFormat)
724{
725 if (aFormat == NULL)
726 return E_POINTER;
727
728 AutoCaller autoCaller (this);
729 CheckComRCReturnRC (autoCaller.rc());
730
731 /* no need to lock, mm.format is const */
732 mm.format.cloneTo (aFormat);
733
734 return S_OK;
735}
736
737STDMETHODIMP HardDisk2::COMGETTER(Type) (HardDiskType_T *aType)
738{
739 if (aType == NULL)
740 return E_POINTER;
741
742 AutoCaller autoCaller (this);
743 CheckComRCReturnRC (autoCaller.rc());
744
745 AutoReadLock alock (this);
746
747 *aType = mm.type;
748
749 return S_OK;
750}
751
752STDMETHODIMP HardDisk2::COMSETTER(Type) (HardDiskType_T aType)
753{
754 AutoCaller autoCaller (this);
755 CheckComRCReturnRC (autoCaller.rc());
756
757 /* VirtualBox::saveSettings() needs a write lock */
758 AutoMultiWriteLock2 alock (mVirtualBox, this);
759
760 switch (m.state)
761 {
762 case MediaState_Created:
763 case MediaState_Inaccessible:
764 break;
765 default:
766 return setStateError();
767 }
768
769 if (mm.type == aType)
770 {
771 /* Nothing to do */
772 return S_OK;
773 }
774
775 /* we access mParent & children() */
776 AutoReadLock treeLock (this->treeLock());
777
778 /* cannot change the type of a differencing hard disk */
779 if (!mParent.isNull())
780 return setError (E_FAIL,
781 tr ("Hard disk '%ls' is a differencing hard disk"),
782 m.locationFull.raw());
783
784 /* cannot change the type of a hard disk being in use */
785 if (m.backRefs.size() != 0)
786 return setError (E_FAIL,
787 tr ("Hard disk '%ls' is attached to %d virtual machines"),
788 m.locationFull.raw(), m.backRefs.size());
789
790 switch (aType)
791 {
792 case HardDiskType_Normal:
793 case HardDiskType_Immutable:
794 {
795 /* normal can be easily converted to imutable and vice versa even
796 * if they have children as long as they are not attached to any
797 * machine themselves */
798 break;
799 }
800 case HardDiskType_Writethrough:
801 {
802 /* cannot change to writethrough if there are children */
803 if (children().size() != 0)
804 return setError (E_FAIL,
805 tr ("Hard disk '%ls' has %d child hard disks"),
806 children().size());
807 break;
808 }
809 default:
810 AssertFailedReturn (E_FAIL);
811 }
812
813 mm.type = aType;
814
815 HRESULT rc = mVirtualBox->saveSettings();
816
817 return rc;
818}
819
820STDMETHODIMP HardDisk2::COMGETTER(Parent) (IHardDisk2 **aParent)
821{
822 if (aParent == NULL)
823 return E_POINTER;
824
825 AutoCaller autoCaller (this);
826 CheckComRCReturnRC (autoCaller.rc());
827
828 /* we access mParent */
829 AutoReadLock treeLock (this->treeLock());
830
831 mParent.queryInterfaceTo (aParent);
832
833 return S_OK;
834}
835
836STDMETHODIMP HardDisk2::COMGETTER(Children) (ComSafeArrayOut (IHardDisk2 *, aChildren))
837{
838 if (ComSafeArrayOutIsNull (aChildren))
839 return E_POINTER;
840
841 AutoCaller autoCaller (this);
842 CheckComRCReturnRC (autoCaller.rc());
843
844 /* we access children */
845 AutoReadLock treeLock (this->treeLock());
846
847 SafeIfaceArray <IHardDisk2> children (this->children());
848 children.detachTo (ComSafeArrayOutArg (aChildren));
849
850 return S_OK;
851}
852
853STDMETHODIMP HardDisk2::COMGETTER(Root) (IHardDisk2 **aRoot)
854{
855 if (aRoot == NULL)
856 return E_POINTER;
857
858 /* root() will do callers/locking */
859
860 root().queryInterfaceTo (aRoot);
861
862 return S_OK;
863}
864
865STDMETHODIMP HardDisk2::COMGETTER(ReadOnly) (BOOL *aReadOnly)
866{
867 if (aReadOnly == NULL)
868 return E_POINTER;
869
870 AutoCaller autoCaller (this);
871 CheckComRCReturnRC (autoCaller.rc());
872
873 /* isRadOnly() will do locking */
874
875 *aReadOnly = isReadOnly();
876
877 return S_OK;
878}
879
880STDMETHODIMP HardDisk2::COMGETTER(LogicalSize) (ULONG64 *aLogicalSize)
881{
882 if (aLogicalSize == NULL)
883 return E_POINTER;
884
885 {
886 AutoCaller autoCaller (this);
887 CheckComRCReturnRC (autoCaller.rc());
888
889 AutoReadLock alock (this);
890
891 /* we access mParent */
892 AutoReadLock treeLock (this->treeLock());
893
894 if (mParent.isNull())
895 {
896 *aLogicalSize = mm.logicalSize;
897
898 return S_OK;
899 }
900 }
901
902 /* We assume that some backend may decide to return a meaningless value in
903 * response to VDGetSize() for differencing hard disks and therefore
904 * always ask the base hard disk ourselves. */
905
906 /* root() will do callers/locking */
907
908 return root()->COMGETTER (LogicalSize) (aLogicalSize);
909}
910
911// IHardDisk2 methods
912////////////////////////////////////////////////////////////////////////////////
913
914STDMETHODIMP HardDisk2::CreateDynamicStorage (ULONG64 aLogicalSize,
915 IProgress **aProgress)
916{
917 if (aProgress == NULL)
918 return E_POINTER;
919
920 AutoCaller autoCaller (this);
921 CheckComRCReturnRC (autoCaller.rc());
922
923 AutoWriteLock alock (this);
924
925 switch (m.state)
926 {
927 case MediaState_NotCreated:
928 break;
929 default:
930 return setStateError();
931 }
932
933 /// @todo NEWMEDIA use backend capabilities to decide if dynamic storage
934 /// is supported
935
936 ComObjPtr <Progress> progress;
937 progress.createObject();
938 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
939 BstrFmt (tr ("Creating dynamic hard disk storage unit '%ls'"),
940 m.location.raw()),
941 FALSE /* aCancelable */);
942 CheckComRCReturnRC (rc);
943
944 /* setup task object and thread to carry out the operation
945 * asynchronously */
946
947 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDynamic));
948 AssertComRCReturnRC (task->autoCaller.rc());
949
950 task->d.size = aLogicalSize;
951
952 rc = task->startThread();
953 CheckComRCReturnRC (rc);
954
955 /* go to Creating state on success */
956 m.state = MediaState_Creating;
957
958 /* task is now owned by taskThread() so release it */
959 task.release();
960
961 /* return progress to the caller */
962 progress.queryInterfaceTo (aProgress);
963
964 return S_OK;
965}
966
967STDMETHODIMP HardDisk2::CreateFixedStorage (ULONG64 aLogicalSize,
968 IProgress **aProgress)
969{
970 if (aProgress == NULL)
971 return E_POINTER;
972
973 AutoCaller autoCaller (this);
974 CheckComRCReturnRC (autoCaller.rc());
975
976 AutoWriteLock alock (this);
977
978 switch (m.state)
979 {
980 case MediaState_NotCreated:
981 break;
982 default:
983 return setStateError();
984 }
985
986 ComObjPtr <Progress> progress;
987 progress.createObject();
988 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
989 BstrFmt (tr ("Creating fixed hard disk storage unit '%ls'"),
990 m.location.raw()),
991 FALSE /* aCancelable */);
992 CheckComRCReturnRC (rc);
993
994 /* setup task object and thread to carry out the operation
995 * asynchronously */
996
997 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateFixed));
998 AssertComRCReturnRC (task->autoCaller.rc());
999
1000 task->d.size = aLogicalSize;
1001
1002 rc = task->startThread();
1003 CheckComRCReturnRC (rc);
1004
1005 /* go to Creating state on success */
1006 m.state = MediaState_Creating;
1007
1008 /* task is now owned by taskThread() so release it */
1009 task.release();
1010
1011 /* return progress to the caller */
1012 progress.queryInterfaceTo (aProgress);
1013
1014 return S_OK;
1015}
1016
1017STDMETHODIMP HardDisk2::DeleteStorage (IProgress **aProgress)
1018{
1019 if (aProgress == NULL)
1020 return E_POINTER;
1021
1022 ComObjPtr <Progress> progress;
1023
1024 HRESULT rc = deleteStorageNoWait (progress);
1025 if (SUCCEEDED (rc))
1026 {
1027 /* return progress to the caller */
1028 progress.queryInterfaceTo (aProgress);
1029 }
1030
1031 return rc;
1032}
1033
1034STDMETHODIMP HardDisk2::CreateDiffStorage (IHardDisk2 *aTarget, IProgress **aProgress)
1035{
1036 if (aTarget == NULL)
1037 return E_INVALIDARG;
1038 if (aProgress == NULL)
1039 return E_POINTER;
1040
1041 AutoCaller autoCaller (this);
1042 CheckComRCReturnRC (autoCaller.rc());
1043
1044 ComObjPtr <HardDisk2> diff;
1045 HRESULT rc = mVirtualBox->cast (aTarget, diff);
1046 CheckComRCReturnRC (rc);
1047
1048 AutoWriteLock alock (this);
1049
1050 if (mm.type == HardDiskType_Writethrough)
1051 return setError (E_FAIL, tr ("Hard disk '%ls' is Writethrough"));
1052
1053 /* We want to be locked for reading as long as our diff child is being
1054 * created */
1055 rc = LockRead (NULL);
1056 CheckComRCReturnRC (rc);
1057
1058 ComObjPtr <Progress> progress;
1059
1060 rc = createDiffStorageNoWait (diff, progress);
1061 if (FAILED (rc))
1062 {
1063 HRESULT rc2 = UnlockRead (NULL);
1064 AssertComRC (rc2);
1065 /* Note: on success, taskThread() will unlock this */
1066 }
1067 else
1068 {
1069 /* return progress to the caller */
1070 progress.queryInterfaceTo (aProgress);
1071 }
1072
1073 return rc;
1074}
1075
1076STDMETHODIMP HardDisk2::MergeTo (INPTR GUIDPARAM aTargetId, IProgress **aProgress)
1077{
1078 AutoCaller autoCaller (this);
1079 CheckComRCReturnRC (autoCaller.rc());
1080
1081 return E_NOTIMPL;
1082}
1083
1084STDMETHODIMP HardDisk2::CloneTo (IHardDisk2 *aTarget, IProgress **aProgress)
1085{
1086 AutoCaller autoCaller (this);
1087 CheckComRCReturnRC (autoCaller.rc());
1088
1089 return E_NOTIMPL;
1090}
1091
1092STDMETHODIMP HardDisk2::FlattenTo (IHardDisk2 *aTarget, IProgress **aProgress)
1093{
1094 AutoCaller autoCaller (this);
1095 CheckComRCReturnRC (autoCaller.rc());
1096
1097 return E_NOTIMPL;
1098}
1099
1100// public methods for internal purposes only
1101////////////////////////////////////////////////////////////////////////////////
1102
1103/**
1104 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
1105 * of this hard disk or any its child and updates the paths if necessary to
1106 * reflect the new location.
1107 *
1108 * @param aOldPath Old path (full).
1109 * @param aNewPath New path (full).
1110 *
1111 * @note Locks treeLock() for reading, this object and all children for writing.
1112 */
1113void HardDisk2::updatePaths (const char *aOldPath, const char *aNewPath)
1114{
1115 AssertReturnVoid (aOldPath);
1116 AssertReturnVoid (aNewPath);
1117
1118 AutoCaller autoCaller (this);
1119 AssertComRCReturnVoid (autoCaller.rc());
1120
1121 AutoWriteLock alock (this);
1122
1123 /* we access children() */
1124 AutoReadLock treeLock (this->treeLock());
1125
1126 updatePath (aOldPath, aNewPath);
1127
1128 /* update paths of all children */
1129 for (List::const_iterator it = children().begin();
1130 it != children().end();
1131 ++ it)
1132 {
1133 (*it)->updatePaths (aOldPath, aNewPath);
1134 }
1135}
1136
1137/**
1138 * Returns the base hard disk of the hard disk chain this hard disk is part of.
1139 *
1140 * The root hard disk is found by walking up the parent-child relationship axis.
1141 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
1142 * returns itself in response to this method.
1143 *
1144 * @param aLevel Where to store the number of ancestors of this hard disk
1145 * (zero for the root), may be @c NULL.
1146 *
1147 * @note Locks treeLock() for reading.
1148 */
1149ComObjPtr <HardDisk2> HardDisk2::root (uint32_t *aLevel /*= NULL*/)
1150{
1151 ComObjPtr <HardDisk2> root;
1152 uint32_t level;
1153
1154 AutoCaller autoCaller (this);
1155 AssertReturn (autoCaller.isOk(), root);
1156
1157 /* we access mParent */
1158 AutoReadLock treeLock (this->treeLock());
1159
1160 root = this;
1161 level = 0;
1162
1163 if (!mParent.isNull())
1164 {
1165 for (;;)
1166 {
1167 AutoCaller rootCaller (root);
1168 AssertReturn (rootCaller.isOk(), root);
1169
1170 if (root->mParent.isNull())
1171 break;
1172
1173 root = root->mParent;
1174 ++ level;
1175 }
1176 }
1177
1178 if (aLevel != NULL)
1179 *aLevel = level;
1180
1181 return root;
1182}
1183
1184/**
1185 * Returns @c true if this hard disk cannot be modified because it has
1186 * dependants (children) or is part of the snapshot. Related to the hard disk
1187 * type and posterity, not to the current media state.
1188 *
1189 * @note Locks this object and treeLock() for reading.
1190 */
1191bool HardDisk2::isReadOnly()
1192{
1193 AutoCaller autoCaller (this);
1194 AssertComRCReturn (autoCaller.rc(), false);
1195
1196 AutoReadLock alock (this);
1197
1198 /* we access children */
1199 AutoReadLock treeLock (this->treeLock());
1200
1201 switch (mm.type)
1202 {
1203 case HardDiskType_Normal:
1204 {
1205 if (children().size() != 0)
1206 return true;
1207
1208 for (BackRefList::const_iterator it = m.backRefs.begin();
1209 it != m.backRefs.end(); ++ it)
1210 if (it->snapshotIds.size() != 0)
1211 return true;
1212
1213 return false;
1214 }
1215 case HardDiskType_Immutable:
1216 {
1217 return true;
1218 }
1219 case HardDiskType_Writethrough:
1220 {
1221 return false;
1222 }
1223 default:
1224 break;
1225 }
1226
1227 AssertFailedReturn (false);
1228}
1229
1230/**
1231 * Saves hard disk data by appending a new <HardDisk> child node to the given
1232 * parent node which can be either <HardDisks> or <HardDisk>.
1233 *
1234 * @param aaParentNode Parent <HardDisks> or <HardDisk> node.
1235 *
1236 * @note Locks this object, treeLock() and children for reading.
1237 */
1238HRESULT HardDisk2::saveSettings (settings::Key &aParentNode)
1239{
1240 using namespace settings;
1241
1242 AssertReturn (!aParentNode.isNull(), E_FAIL);
1243
1244 AutoCaller autoCaller (this);
1245 CheckComRCReturnRC (autoCaller.rc());
1246
1247 AutoReadLock alock (this);
1248
1249 /* we access mParent */
1250 AutoReadLock treeLock (this->treeLock());
1251
1252 Key diskNode = aParentNode.appendKey ("HardDisk");
1253 /* required */
1254 diskNode.setValue <Guid> ("uuid", m.id);
1255 /* required (note: the original locaiton, not full) */
1256 diskNode.setValue <Bstr> ("location", m.location);
1257 /* required */
1258 diskNode.setValue <Bstr> ("format", mm.format);
1259 /* optional */
1260 if (!m.description.isNull())
1261 {
1262 Key descNode = diskNode.createKey ("Description");
1263 descNode.setKeyValue <Bstr> (m.description);
1264 }
1265
1266 /* only for base hard disks */
1267 if (mParent.isNull())
1268 {
1269 const char *type =
1270 mm.type == HardDiskType_Normal ? "Normal" :
1271 mm.type == HardDiskType_Immutable ? "Immutable" :
1272 mm.type == HardDiskType_Writethrough ? "Writethrough" : NULL;
1273 Assert (type != NULL);
1274 diskNode.setStringValue ("type", type);
1275 }
1276
1277 /* save all children */
1278 for (List::const_iterator it = children().begin();
1279 it != children().end();
1280 ++ it)
1281 {
1282 HRESULT rc = (*it)->saveSettings (diskNode);
1283 AssertComRCReturnRC (rc);
1284 }
1285
1286 return S_OK;
1287}
1288
1289/**
1290 * Compares the location of this hard disk to the given location.
1291 *
1292 * The comparison takes the location details into account. For example, if the
1293 * location is a file in the host's filesystem, a case insensitive comparison
1294 * will be performed for case insensitive filesystems.
1295 *
1296 * @param aLocation Location to compare to (as is).
1297 * @param aResult Where to store the result of comparison: 0 if locations
1298 * are equal, 1 if this object's location is greater than
1299 * the specified location, and -1 otherwise.
1300 */
1301HRESULT HardDisk2::compareLocationTo (const char *aLocation, int &aResult)
1302{
1303 AutoCaller autoCaller (this);
1304 AssertComRCReturnRC (autoCaller.rc());
1305
1306 AutoReadLock alock (this);
1307
1308 Utf8Str locationFull (m.locationFull);
1309
1310 /// @todo NEWMEDIA delegate the comparison to the backend?
1311
1312 if (isFileLocation (locationFull))
1313 {
1314 Utf8Str location (aLocation);
1315
1316 /* For locations represented by files, append the default path if
1317 * only the name is given, and then get the full path. */
1318 if (!RTPathHavePath (aLocation))
1319 {
1320 AutoReadLock propsLock (mVirtualBox->systemProperties());
1321 location = Utf8StrFmt ("%ls%c%s",
1322 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
1323 RTPATH_DELIMITER, aLocation);
1324 }
1325
1326 int vrc = mVirtualBox->calculateFullPath (location, location);
1327 if (RT_FAILURE (vrc))
1328 return setError (E_FAIL,
1329 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
1330 location.raw(), vrc);
1331
1332 aResult = RTPathCompare (locationFull, location);
1333 }
1334 else
1335 aResult = locationFull.compare (aLocation);
1336
1337 return S_OK;
1338}
1339
1340/**
1341 * Returns a short version of the location attribute.
1342 *
1343 * Reimplements MediumBase::name() to specially treat non-FS-path locations.
1344 *
1345 * @note Must be called from under this object's read or write lock.
1346 */
1347Utf8Str HardDisk2::name()
1348{
1349 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to requiest
1350 /// this information from the VD backend)
1351
1352 Utf8Str location (m.locationFull);
1353
1354 Utf8Str name = RTPathFilename (location);
1355 return name;
1356}
1357
1358/**
1359 * Returns @c true if the given location is a file in the host's filesystem.
1360 *
1361 * @param aLocation Location to check.
1362 */
1363/*static*/
1364bool HardDisk2::isFileLocation (const char *aLocation)
1365{
1366 /// @todo NEWMEDIA need some library that deals with URLs in a generic way
1367
1368 if (aLocation == NULL)
1369 return false;
1370
1371 size_t len = strlen (aLocation);
1372
1373 /* unix-like paths */
1374 if (len >= 1 && RTPATH_IS_SLASH (aLocation [0]))
1375 return true;
1376
1377 /* dos-like paths */
1378 if (len >= 2 && RTPATH_IS_VOLSEP (aLocation [1]) &&
1379 ((aLocation [0] >= 'A' && aLocation [0] <= 'Z') ||
1380 (aLocation [0] >= 'a' && aLocation [0] <= 'z')))
1381 return true;
1382
1383 /* if there is a '<protocol>:' suffux which is not 'file:', return false */
1384 const char *s = strchr (aLocation, ':');
1385 if (s != NULL)
1386 {
1387 if (s - aLocation != 4)
1388 return false;
1389 if ((aLocation [0] != 'f' && aLocation [0] == 'F') ||
1390 (aLocation [1] != 'i' && aLocation [1] == 'I') ||
1391 (aLocation [2] != 'l' && aLocation [2] == 'L') ||
1392 (aLocation [3] != 'e' && aLocation [3] == 'E'))
1393 return false;
1394 }
1395
1396 /* everything else is treaten as file */
1397 return true;
1398}
1399
1400/**
1401 * Checks that this hard disk may be discarded and performs necessary state
1402 * changes.
1403 *
1404 * This method is to be called prior to calling the #discrad() to perform
1405 * necessary consistency checks and place involved hard disks to appropriate
1406 * states. If #discard() is not called or fails, the state modifications
1407 * performed by this method must be undone by #cancelDiscard().
1408 *
1409 * See #discard() for more info about discarding hard disks.
1410 *
1411 * @param aChain Where to store the created merge chain (may return NULL
1412 * if no real merge is necessary).
1413 *
1414 * @note Locks treeLock() for reading. Locks this object, aTarget and all
1415 * intermediate hard disks for writing.
1416 */
1417HRESULT HardDisk2::prepareDiscard (MergeChain * &aChain)
1418{
1419 AutoCaller autoCaller (this);
1420 AssertComRCReturnRC (autoCaller.rc());
1421
1422 aChain = NULL;
1423
1424 AutoWriteLock alock (this);
1425
1426 /* we access mParent & children() */
1427 AutoReadLock treeLock (this->treeLock());
1428
1429 AssertReturn (mm.type == HardDiskType_Normal, E_FAIL);
1430
1431 if (children().size() == 0)
1432 {
1433 /* special treatment of the last hard disk in the chain: */
1434
1435 if (mParent.isNull())
1436 {
1437 /* lock only, to prevent any usage; discard() will unlock */
1438 return LockWrite (NULL);
1439 }
1440
1441 /* the differencing hard disk w/o children will be deleted, protect it
1442 * from attaching to other VMs (this is why Deleting) */
1443
1444 switch (m.state)
1445 {
1446 case MediaState_Created:
1447 m.state = MediaState_Deleting;
1448 break;
1449 default:
1450 return setStateError();
1451 }
1452
1453 /* aChain is intentionally NULL here */
1454
1455 return S_OK;
1456 }
1457
1458 /* not going multi-merge as it's too expensive */
1459 if (children().size() > 1)
1460 return setError (E_FAIL,
1461 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
1462 m.locationFull.raw(), children().size());
1463
1464 /* this is a read-only hard disk with children; it must be associated with
1465 * exactly one snapshot (when the snapshot is being taken, none of the
1466 * current VM's hard disks may be attached to other VMs). Note that by the
1467 * time when discard() is called, there must be no any attachments at all
1468 * (the code calling prepareDiscard() should detach). */
1469 AssertReturn (m.backRefs.size() == 1 &&
1470 !m.backRefs.front().inCurState &&
1471 m.backRefs.front().snapshotIds.size() == 1, E_FAIL);
1472
1473 ComObjPtr <HardDisk2> child = children().front();
1474
1475 /* we keep this locked, so lock the affected child to make sure the lock
1476 * order is correct when calling prepareMergeTo() */
1477 AutoWriteLock childLock (child);
1478
1479 /* delegate the rest to the profi */
1480 if (mParent.isNull())
1481 {
1482 /* base hard disk, backward merge */
1483
1484 Assert (child->m.backRefs.size() == 1);
1485 if (child->m.backRefs.front().machineId != m.backRefs.front().machineId)
1486 {
1487 /* backward merge is too tricky, we'll just detach on discard, so
1488 * lock only, to prevent any usage; discard() will only unlock
1489 * (since we return NULL in aChain) */
1490 return LockWrite (NULL);
1491 }
1492
1493 return child->prepareMergeTo (this, aChain,
1494 true /* aIgnoreAttachments */);
1495 }
1496 else
1497 {
1498 /* forward merge */
1499 return prepareMergeTo (child, aChain,
1500 true /* aIgnoreAttachments */);
1501 }
1502}
1503
1504/**
1505 * Discards this hard disk.
1506 *
1507 * Discarding the hard disk is merging its contents to its differencing child
1508 * hard disk (forward merge) or contents of its child hard disk to itself
1509 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
1510 * a differencing hard disk w/o children, then it will be simply deleted.
1511 * Calling this method on a base hard disk w/o children will do nothing and
1512 * silently succeed. If this hard disk has more than one child, the method will
1513 * currently return an error (since merging in this case would be too expensive
1514 * and result in data duplication).
1515 *
1516 * When the backward merge takes place (i.e. this hard disk is a target) then,
1517 * on success, this hard disk will automatically replace the differencing child
1518 * hard disk used as a source (which will then be deleted) in the attachment
1519 * this child hard disk is associated with. This will happen only if both hard
1520 * disks belong to the same machine because otherwise such a replace would be
1521 * too tricky and could be not expected by the other machine. Same relates to a
1522 * case when the child hard disk is not associated with any machine at all. When
1523 * the backward merge is not applied, the method behaves as if the base hard
1524 * disk were not attached at all -- i.e. simply detaches it from the machine but
1525 * leaves the hard disk chain intact.
1526 *
1527 * This method is basically a wrapper around #mergeTo() that selects the correct
1528 * merge direction and performs additional actions as described above and.
1529 *
1530 * Note that this method will not return until the merge operation is complete
1531 * (which may be quite time consuming depending on the size of the merged hard
1532 * disks).
1533 *
1534 * Note that #prepareDiscard() must be called before calling this method. If
1535 * this method returns a failure, the caller must call #cancelDiscard(). On
1536 * success, #cancelDiscard() must not be called (this method will perform all
1537 * necessary steps such as resetting states of all involved hard disks and
1538 * deleting @a aChain).
1539 *
1540 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1541 * no real merge takes place).
1542 *
1543 * @note Locks the hard disks from the chain for writing. Locks the machine
1544 * object when the backward merge takes place. Locks treeLock() lock for
1545 * reading or writing.
1546 */
1547HRESULT HardDisk2::discard (ComObjPtr <Progress> &aProgress, MergeChain *aChain)
1548{
1549 AssertReturn (!aProgress.isNull(), E_FAIL);
1550
1551 ComObjPtr <HardDisk2> hdFrom;
1552
1553 HRESULT rc = S_OK;
1554
1555 {
1556 AutoCaller autoCaller (this);
1557 AssertComRCReturnRC (autoCaller.rc());
1558
1559 aProgress->advanceOperation (BstrFmt (
1560 tr ("Discarding hard disk '%s'"), name().raw()));
1561
1562 if (aChain == NULL)
1563 {
1564 AutoWriteLock alock (this);
1565
1566 /* we access mParent & children() */
1567 AutoReadLock treeLock (this->treeLock());
1568
1569 Assert (children().size() == 0);
1570
1571 /* special treatment of the last hard disk in the chain: */
1572
1573 if (mParent.isNull())
1574 {
1575 rc = UnlockWrite (NULL);
1576 AssertComRC (rc);
1577 return rc;
1578 }
1579
1580 /* delete the differencing hard disk w/o children */
1581
1582 Assert (m.state == MediaState_Deleting);
1583
1584 /* go back to Created since deleteStorage() expects this state */
1585 m.state = MediaState_Created;
1586
1587 hdFrom = this;
1588
1589 rc = deleteStorageAndWait (&aProgress);
1590 }
1591 else
1592 {
1593 hdFrom = aChain->source();
1594
1595 rc = hdFrom->mergeToAndWait (aChain, &aProgress);
1596 }
1597 }
1598
1599 if (SUCCEEDED (rc))
1600 {
1601 /* mergeToAndWait() cannot uninitialize the initiator because of
1602 * possible AutoCallers on the current thread, deleteStorageAndWait()
1603 * doesn't do it either; do it ourselves */
1604 hdFrom->uninit();
1605 }
1606
1607 return rc;
1608}
1609
1610/**
1611 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
1612 * or fails. Frees memory occupied by @a aChain.
1613 *
1614 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1615 * no real merge takes place).
1616 *
1617 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
1618 * reading.
1619 */
1620void HardDisk2::cancelDiscard (MergeChain *aChain)
1621{
1622 AutoCaller autoCaller (this);
1623 AssertComRCReturnVoid (autoCaller.rc());
1624
1625 if (aChain == NULL)
1626 {
1627 AutoWriteLock alock (this);
1628
1629 /* we access mParent & children() */
1630 AutoReadLock treeLock (this->treeLock());
1631
1632 Assert (children().size() == 0);
1633
1634 /* special treatment of the last hard disk in the chain: */
1635
1636 if (mParent.isNull())
1637 {
1638 HRESULT rc = UnlockWrite (NULL);
1639 AssertComRC (rc);
1640 return;
1641 }
1642
1643 /* the differencing hard disk w/o children will be deleted, protect it
1644 * from attaching to other VMs (this is why Deleting) */
1645
1646 Assert (m.state == MediaState_Deleting);
1647 m.state = MediaState_Created;
1648
1649 return;
1650 }
1651
1652 /* delegate the rest to the profi */
1653 cancelMergeTo (aChain);
1654}
1655
1656// protected methods
1657////////////////////////////////////////////////////////////////////////////////
1658
1659/**
1660 * Deletes the hard disk storage unit.
1661 *
1662 * If @a aProgress is not NULL but the object it points to is @c null then a new
1663 * progress object will be created and assigned to @a *aProgress on success,
1664 * otherwise the existing progress object is used. If Progress is NULL, then no
1665 * progress object is created/used at all.
1666 *
1667 * When @a aWait is @c false, this method will create a thread to perform the
1668 * delete operation asynchronously and will return immediately. Otherwise, it
1669 * will perform the operation on the calling thread and will not return to the
1670 * caller until the operation is completed. Note that @a aProgress cannot be
1671 * NULL when @a aWait is @c false (this method will assert in this case).
1672 *
1673 * @param aProgress Where to find/store a Progress object to track operation
1674 * completion.
1675 * @param aWait @c true if this method should block instead of creating
1676 * an asynchronous thread.
1677 *
1678 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
1679 * writing.
1680 */
1681HRESULT HardDisk2::deleteStorage (ComObjPtr <Progress> *aProgress, bool aWait)
1682{
1683 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
1684
1685 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
1686 * ourselves atomically after detecting that deletion is possible to make
1687 * sure that we don't do that after another thread has done
1688 * VirtualBox::findHardDisk2() but before it starts using us (provided that
1689 * it holds a mVirtualBox lock too of course). */
1690
1691 AutoWriteLock vboxLock (mVirtualBox);
1692
1693 AutoWriteLock alock (this);
1694
1695 switch (m.state)
1696 {
1697 case MediaState_Created:
1698 break;
1699 default:
1700 return setStateError();
1701 }
1702
1703 if (m.backRefs.size() != 0)
1704 return setError (E_FAIL,
1705 tr ("Hard disk '%ls' is attached to %d virtual machines"),
1706 m.locationFull.raw(), m.backRefs.size());
1707
1708 HRESULT rc = canClose();
1709 CheckComRCReturnRC (rc);
1710
1711 /* go to Deleting state before leaving the lock */
1712 m.state = MediaState_Deleting;
1713
1714 /* we need to leave this object's write lock now because of
1715 * unregisterWithVirtualBox() that locks treeLock() for writing */
1716 alock.leave();
1717
1718 /* try to remove from the list of known hard disks before performing actual
1719 * deletion (we favor the consistency of the media registry in the first
1720 * place which would have been broken if unregisterWithVirtualBox() failed
1721 * after we successfully deleted the storage) */
1722
1723 rc = unregisterWithVirtualBox();
1724
1725 alock.enter();
1726
1727 /* restore the state because we may fail below; we will set it later again*/
1728 m.state = MediaState_Created;
1729
1730 CheckComRCReturnRC (rc);
1731
1732 ComObjPtr <Progress> progress;
1733
1734 if (aProgress != NULL)
1735 {
1736 /* use the existing progress object... */
1737 progress = *aProgress;
1738
1739 /* ...but create a new one if it is null */
1740 if (progress.isNull())
1741 {
1742 progress.createObject();
1743 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1744 BstrFmt (tr ("Deleting hard disk storage unit '%ls'"),
1745 name().raw()),
1746 FALSE /* aCancelable */);
1747 CheckComRCReturnRC (rc);
1748 }
1749 }
1750
1751 std::auto_ptr <Task> task (new Task (this, progress, Task::Delete));
1752 AssertComRCReturnRC (task->autoCaller.rc());
1753
1754 if (aWait)
1755 {
1756 /* go to Deleting state before starting the task */
1757 m.state = MediaState_Deleting;
1758
1759 rc = task->runNow();
1760 }
1761 else
1762 {
1763 rc = task->startThread();
1764 CheckComRCReturnRC (rc);
1765
1766 /* go to Deleting state before leaving the lock */
1767 m.state = MediaState_Deleting;
1768 }
1769
1770 /* task is now owned (or already deleted) by taskThread() so release it */
1771 task.release();
1772
1773 if (aProgress != NULL)
1774 {
1775 /* return progress to the caller */
1776 *aProgress = progress;
1777 }
1778
1779 return rc;
1780}
1781
1782/**
1783 * Creates a new differencing storage unit using the given target hard disk's
1784 * format and the location. Note that @c aTarget must be NotCreated.
1785 *
1786 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
1787 * this hard disk for reading assuming that the caller has already done so. This
1788 * is used when taking an online snaopshot (where all origial hard disks are
1789 * locked for writing and must remain such). Note however that if @a aWait is
1790 * @c false and this method returns a success then the thread started by
1791 * this method will* unlock the hard disk (unless it is in
1792 * MediaState_LockedWrite state) so make sure the hard disk is either in
1793 * MediaState_LockedWrite or call #LockRead() before calling this method! If @a
1794 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
1795 * make sure you do it yourself as needed.
1796 *
1797 * If @a aProgress is not NULL but the object it points to is @c null then a new
1798 * progress object will be created and assigned to @a *aProgress on success,
1799 * otherwise the existing progress object is used. If Progress is NULL, then no
1800 * progress object is created/used at all.
1801 *
1802 * When @a aWait is @c false, this method will create a thread to perform the
1803 * create operation asynchronously and will return immediately. Otherwise, it
1804 * will perform the operation on the calling thread and will not return to the
1805 * caller until the operation is completed. Note that @a aProgress cannot be
1806 * NULL when @a aWait is @c false (this method will assert in this case).
1807 *
1808 * @param aTarget Target hard disk.
1809 * @param aProgress Where to find/store a Progress object to track operation
1810 * completion.
1811 * @param aWait @c true if this method should block instead of creating
1812 * an asynchronous thread.
1813 *
1814 * @note Locks this object and aTarget for writing.
1815 */
1816HRESULT HardDisk2::createDiffStorage (ComObjPtr <HardDisk2> &aTarget,
1817 ComObjPtr <Progress> *aProgress,
1818 bool aWait)
1819{
1820 AssertReturn (!aTarget.isNull(), E_FAIL);
1821 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
1822
1823 AutoCaller autoCaller (this);
1824 CheckComRCReturnRC (autoCaller.rc());
1825
1826 AutoCaller targetCaller (aTarget);
1827 CheckComRCReturnRC (targetCaller.rc());
1828
1829 AutoMultiWriteLock2 alock (this, aTarget);
1830
1831 AssertReturn (mm.type != HardDiskType_Writethrough, E_FAIL);
1832
1833 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
1834 AssertReturn (m.state == MediaState_LockedRead ||
1835 m.state == MediaState_LockedWrite, E_FAIL);
1836
1837 if (aTarget->m.state != MediaState_NotCreated)
1838 return aTarget->setStateError();
1839
1840 HRESULT rc = S_OK;
1841
1842 /* check that the hard disk is not attached to any VM in the current state*/
1843 for (BackRefList::const_iterator it = m.backRefs.begin();
1844 it != m.backRefs.end(); ++ it)
1845 {
1846 if (it->inCurState)
1847 {
1848 /* Note: when a VM snapshot is being taken, all normal hard disks
1849 * attached to the VM in the current state will be, as an exception,
1850 * also associated with the snapshot which is about to create (see
1851 * SnapshotMachine::init()) before deassociating them from the
1852 * current state (which takes place only on success in
1853 * Machine::fixupHardDisks2()), so that the size of snapshotIds
1854 * will be 1 in this case. The given condition is used to filter out
1855 * this legal situatinon and do not report an error. */
1856
1857 if (it->snapshotIds.size() == 0)
1858 {
1859 return setError (E_FAIL,
1860 tr ("Hard disk '%ls' is attached to a virtual machine "
1861 "with UUID {%RTuuid}. No differencing hard disks "
1862 "based on it may be created until it is detached"),
1863 m.location.raw(), it->machineId.raw());
1864 }
1865
1866 Assert (it->snapshotIds.size() == 1);
1867 }
1868 }
1869
1870 ComObjPtr <Progress> progress;
1871
1872 if (aProgress != NULL)
1873 {
1874 /* use the existing progress object... */
1875 progress = *aProgress;
1876
1877 /* ...but create a new one if it is null */
1878 if (progress.isNull())
1879 {
1880 progress.createObject();
1881 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1882 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
1883 aTarget->name().raw()),
1884 FALSE /* aCancelable */);
1885 CheckComRCReturnRC (rc);
1886 }
1887 }
1888
1889 /* setup task object and thread to carry out the operation
1890 * asynchronously */
1891
1892 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
1893 AssertComRCReturnRC (task->autoCaller.rc());
1894
1895 task->setData (aTarget);
1896
1897 /* register a task (it will deregister itself when done) */
1898 ++ mm.numCreateDiffTasks;
1899 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
1900
1901 if (aWait)
1902 {
1903 /* go to Creating state before starting the task */
1904 aTarget->m.state = MediaState_Creating;
1905
1906 rc = task->runNow();
1907 }
1908 else
1909 {
1910 rc = task->startThread();
1911 CheckComRCReturnRC (rc);
1912
1913 /* go to Creating state before leaving the lock */
1914 aTarget->m.state = MediaState_Creating;
1915 }
1916
1917 /* task is now owned (or already deleted) by taskThread() so release it */
1918 task.release();
1919
1920 if (aProgress != NULL)
1921 {
1922 /* return progress to the caller */
1923 *aProgress = progress;
1924 }
1925
1926 return rc;
1927}
1928
1929/**
1930 * Prepares this (source) hard disk, target hard disk and all intermediate hard
1931 * disks for the merge operation.
1932 *
1933 * This method is to be called prior to calling the #mergeTo() to perform
1934 * necessary consistency checks and place involved hard disks to appropriate
1935 * states. If #mergeTo() is not called or fails, the state modifications
1936 * performed by this method must be undone by #cancelMergeTo().
1937 *
1938 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
1939 * responsibility to detach the source and all intermediate hard disks before
1940 * calling #mergeTo() (which will fail otherwise).
1941 *
1942 * See #mergeTo() for more information about merging.
1943 *
1944 * @param aTarget Target hard disk.
1945 * @param aChain Where to store the created merge chain.
1946 * @param aIgnoreAttachments Don't check if the source or any intermediate
1947 * hard disk is attached to any VM.
1948 *
1949 * @note Locks treeLock() for reading. Locks this object, aTarget and all
1950 * intermediate hard disks for writing.
1951 */
1952HRESULT HardDisk2::prepareMergeTo (HardDisk2 *aTarget,
1953 MergeChain * &aChain,
1954 bool aIgnoreAttachments /*= false*/)
1955{
1956 AssertReturn (aTarget != NULL, E_FAIL);
1957
1958 AutoCaller autoCaller (this);
1959 AssertComRCReturnRC (autoCaller.rc());
1960
1961 AutoCaller targetCaller (aTarget);
1962 AssertComRCReturnRC (targetCaller.rc());
1963
1964 aChain = NULL;
1965
1966 /* we walk the tree */
1967 AutoReadLock treeLock (this->treeLock());
1968
1969 HRESULT rc = S_OK;
1970
1971 /* detect the merge direction */
1972 bool forward;
1973 {
1974 HardDisk2 *parent = mParent;
1975 while (parent != NULL && parent != aTarget)
1976 parent = parent->mParent;
1977 if (parent == aTarget)
1978 forward = false;
1979 else
1980 {
1981 parent = aTarget->mParent;
1982 while (parent != NULL && parent != this)
1983 parent = parent->mParent;
1984 if (parent == this)
1985 forward = true;
1986 else
1987 {
1988 Bstr tgtLoc;
1989 {
1990 AutoReadLock alock (this);
1991 tgtLoc = aTarget->locationFull();
1992 }
1993
1994 AutoReadLock alock (this);
1995 return setError (E_FAIL,
1996 tr ("Hard disks '%ls' and '%ls' are unrelated"),
1997 m.locationFull.raw(), tgtLoc.raw());
1998 }
1999 }
2000 }
2001
2002 /* build the chain (will do necessary checks and state changes) */
2003 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2004 aIgnoreAttachments));
2005 {
2006 HardDisk2 *last = forward ? aTarget : this;
2007 HardDisk2 *first = forward ? this : aTarget;
2008
2009 for (;;)
2010 {
2011 if (last == aTarget)
2012 rc = chain->addTarget (last);
2013 else if (last == this)
2014 rc = chain->addSource (last);
2015 else
2016 rc = chain->addIntermediate (last);
2017 CheckComRCReturnRC (rc);
2018
2019 if (last == first)
2020 break;
2021
2022 last = last->mParent;
2023 }
2024 }
2025
2026 aChain = chain.release();
2027
2028 return S_OK;
2029}
2030
2031/**
2032 * Merges this hard disk to the specified hard disk which must be either its
2033 * direct ancestor or descendant.
2034 *
2035 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2036 * get two varians of the merge operation:
2037 *
2038 * forward merge
2039 * ------------------------->
2040 * [Extra] <- SOURCE <- Intermediate <- TARGET
2041 * Any Del Del LockWr
2042 *
2043 *
2044 * backward merge
2045 * <-------------------------
2046 * TARGET <- Intermediate <- SOURCE <- [Extra]
2047 * LockWr Del Del LockWr
2048 *
2049 * Each scheme shows the involved hard disks on the hard disk chain where
2050 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2051 * the hard disk must have at a time of the mergeTo() call.
2052 *
2053 * The hard disks in the square braces may be absent (e.g. when the forward
2054 * operation takes place and SOURCE is the base hard disk, or when the backward
2055 * merge operation takes place and TARGET is the last child in the chain) but if
2056 * they present they are involved too as shown.
2057 *
2058 * Nor the source hard disk neither intermediate hard disks may be attached to
2059 * any VM directly or in the snapshot, otherwise this method will assert.
2060 *
2061 * The #prepareMergeTo() method must be called prior to this method to place all
2062 * involved to necessary states and perform other consistency checks.
2063 *
2064 * If @a aWait is @c true then this method will perform the operation on the
2065 * calling thread and will not return to the caller until the operation is
2066 * completed. When this method succeeds, all intermediate hard disk objects in
2067 * the chain will be uninitialized, the state of the target hard disk (and all
2068 * involved extra hard disks) will be restored and @a aChain will be deleted.
2069 * Note that this (source) hard disk is not uninitialized because of possible
2070 * AutoCaller instances held by the caller of this method on the current thread.
2071 * It's therefore the responsibility of the caller to call HardDisk2::uninit()
2072 * after releasing all callers in this case!
2073 *
2074 * If @a aWait is @c false then this method will crea,te a thread to perform the
2075 * create operation asynchronously and will return immediately. If the operation
2076 * succeeds, the thread will uninitialize the source hard disk object and all
2077 * intermediate hard disk objects in the chain, reset the state of the target
2078 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2079 * operation fails, the thread will only reset the states of all involved hard
2080 * disks and delete @a aChain.
2081 *
2082 * When this method fails (regardless of the @a aWait mode), it is a caller's
2083 * responsiblity to undo state changes and delete @a aChain using
2084 * #cancelMergeTo().
2085 *
2086 * If @a aProgress is not NULL but the object it points to is @c null then a new
2087 * progress object will be created and assigned to @a *aProgress on success,
2088 * otherwise the existing progress object is used. If Progress is NULL, then no
2089 * progress object is created/used at all. Note that @a aProgress cannot be
2090 * NULL when @a aWait is @c false (this method will assert in this case).
2091 *
2092 * @param aChain Merge chain created by #prepareMergeTo().
2093 * @param aProgress Where to find/store a Progress object to track operation
2094 * completion.
2095 * @param aWait @c true if this method should block instead of creating
2096 * an asynchronous thread.
2097 *
2098 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2099 * for writing.
2100 */
2101HRESULT HardDisk2::mergeTo (MergeChain *aChain,
2102 ComObjPtr <Progress> *aProgress,
2103 bool aWait)
2104{
2105 AssertReturn (aChain != NULL, E_FAIL);
2106 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2107
2108 AutoCaller autoCaller (this);
2109 CheckComRCReturnRC (autoCaller.rc());
2110
2111 HRESULT rc = S_OK;
2112
2113 ComObjPtr <Progress> progress;
2114
2115 if (aProgress != NULL)
2116 {
2117 /* use the existing progress object... */
2118 progress = *aProgress;
2119
2120 /* ...but create a new one if it is null */
2121 if (progress.isNull())
2122 {
2123 AutoReadLock alock (this);
2124
2125 progress.createObject();
2126 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
2127 BstrFmt (tr ("Merging hard disk '%ls' to '%ls'"),
2128 name().raw(), aChain->target()->name().raw()),
2129 FALSE /* aCancelable */);
2130 CheckComRCReturnRC (rc);
2131 }
2132 }
2133
2134 /* setup task object and thread to carry out the operation
2135 * asynchronously */
2136
2137 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2138 AssertComRCReturnRC (task->autoCaller.rc());
2139
2140 task->setData (aChain);
2141
2142 /* Note: task owns aChain (will delete it when not needed) in all cases
2143 * except when @a aWait is @c true and runNow() fails -- in this case
2144 * aChain will be left away because cancelMergeTo() will be applied by the
2145 * caller on it as it is required in the documentation above */
2146
2147 if (aWait)
2148 {
2149 rc = task->runNow();
2150 }
2151 else
2152 {
2153 rc = task->startThread();
2154 CheckComRCReturnRC (rc);
2155 }
2156
2157 /* task is now owned (or already deleted) by taskThread() so release it */
2158 task.release();
2159
2160 if (aProgress != NULL)
2161 {
2162 /* return progress to the caller */
2163 *aProgress = progress;
2164 }
2165
2166 return rc;
2167}
2168
2169/**
2170 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2171 * or fails. Frees memory occupied by @a aChain.
2172 *
2173 * @param aChain Merge chain created by #prepareMergeTo().
2174 *
2175 * @note Locks the hard disks from the chain for writing.
2176 */
2177void HardDisk2::cancelMergeTo (MergeChain *aChain)
2178{
2179 AutoCaller autoCaller (this);
2180 AssertComRCReturnVoid (autoCaller.rc());
2181
2182 AssertReturnVoid (aChain != NULL);
2183
2184 /* the destructor will do the thing */
2185 delete aChain;
2186}
2187
2188// private methods
2189////////////////////////////////////////////////////////////////////////////////
2190
2191/**
2192 * Sets the value of m.location and calculates the value of m.locationFull.
2193 *
2194 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2195 * locations and to prepend the default hard disk folder if the given location
2196 * string does not contain any path information at all.
2197 *
2198 * Also, if the specified location is a file path that ends with '/' then the
2199 * file name part will be generated by this method automatically in the format
2200 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2201 * and assign to this medium, and <ext> is the default extension for this
2202 * medium's storage format. Note that this procedure requires the media state to
2203 * be NotCreated and will return a faiulre otherwise.
2204 *
2205 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2206 * then it can be relative to the VirtualBox home directory.
2207 *
2208 * @note Must be called from under this object's write lock.
2209 */
2210HRESULT HardDisk2::setLocation (const BSTR aLocation)
2211{
2212 if (aLocation == NULL)
2213 return E_INVALIDARG;
2214
2215 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to request
2216 /// this information from the VD backend)
2217
2218 Utf8Str location (aLocation);
2219
2220 Guid id;
2221
2222 if (RTPathFilename (location) == NULL)
2223 {
2224 /* no file name is given (either an empty string or ends with a slash),
2225 * generate a new UUID + file name if the state allows this */
2226
2227 if (m.state == MediaState_NotCreated)
2228 {
2229 id.create();
2230
2231 /// @todo NEWMEDIA use the default extension for the given VD backend
2232 location = Utf8StrFmt ("%s{%RTuuid}.vdi", location.raw(), id.raw());
2233 }
2234 else
2235 {
2236 /* it's an error to not have a file name :) */
2237 return setError (E_FAIL,
2238 tr ("Hard disk storage file location '%s' does not contain "
2239 "a file name"),
2240 location.raw());
2241 }
2242 }
2243
2244 /* append the default folder if no path is given */
2245 if (!RTPathHavePath (location))
2246 {
2247 AutoReadLock propsLock (mVirtualBox->systemProperties());
2248 location = Utf8StrFmt ("%ls%c%s",
2249 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2250 RTPATH_DELIMITER,
2251 location.raw());
2252 }
2253
2254 /* get the full file name */
2255 Utf8Str locationFull;
2256 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
2257 if (RT_FAILURE (vrc))
2258 return setError (E_FAIL,
2259 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2260 location.raw(), vrc);
2261
2262 m.location = location;
2263 m.locationFull = locationFull;
2264
2265 /* assign a new UUID if we generated it */
2266 if (!id.isEmpty())
2267 unconst (m.id) = id;
2268
2269 return S_OK;
2270}
2271/**
2272 * Queries information from the image file.
2273 *
2274 * As a result of this call, the accessibility state and data members such as
2275 * size and description will be updated with the current information.
2276 *
2277 * Reimplements MediumBase::queryInfo() to query hard disk information using the
2278 * VD backend interface.
2279 *
2280 * @note This method may block during a system I/O call that checks storage
2281 * accessibility.
2282 *
2283 * @note Locks treeLock() for reading and writing (for new diff media checked
2284 * for the first time). Locks mParent for reading. Locks this object for
2285 * writing.
2286 */
2287HRESULT HardDisk2::queryInfo()
2288{
2289 AutoWriteLock alock (this);
2290
2291 AssertReturn (m.state == MediaState_Created ||
2292 m.state == MediaState_Inaccessible ||
2293 m.state == MediaState_LockedRead ||
2294 m.state == MediaState_LockedWrite,
2295 E_FAIL);
2296
2297 HRESULT rc = S_OK;
2298
2299 int vrc = VINF_SUCCESS;
2300
2301 /* check if a blocking queryInfo() call is in progress on some other thread,
2302 * and wait for it to finish if so instead of querying data ourselves */
2303 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
2304 {
2305 Assert (m.state == MediaState_LockedRead);
2306
2307 ++ m.queryInfoCallers;
2308 alock.leave();
2309
2310 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
2311
2312 alock.enter();
2313 -- m.queryInfoCallers;
2314
2315 if (m.queryInfoCallers == 0)
2316 {
2317 /* last waiting caller deletes the semaphore */
2318 RTSemEventMultiDestroy (m.queryInfoSem);
2319 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2320 }
2321
2322 AssertRC (vrc);
2323
2324 return S_OK;
2325 }
2326
2327 /* lazily create a semaphore for possible callers */
2328 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
2329 ComAssertRCRet (vrc, E_FAIL);
2330
2331 bool tempStateSet = false;
2332 if (m.state != MediaState_LockedRead &&
2333 m.state != MediaState_LockedWrite)
2334 {
2335 /* Cause other methods to prevent any modifications before leaving the
2336 * lock. Note that clients will never see this temporary state change
2337 * since any COMGETTER(State) is (or will be) blocked until we finish
2338 * and restore the actual state. */
2339 m.state = MediaState_LockedRead;
2340 tempStateSet = true;
2341 }
2342
2343 /* leave the lock before a blocking operation */
2344 alock.leave();
2345
2346 bool success = false;
2347 Utf8Str lastAccessError;
2348
2349 try
2350 {
2351 Utf8Str location (m.locationFull);
2352
2353 /* are we dealing with an unknown (just opened) image? */
2354 bool isNew = mm.format.isNull();
2355
2356 if (isNew)
2357 {
2358 /* detect the backend from the storage unit */
2359 char *backendName = NULL;
2360 vrc = VDGetFormat (location, &backendName);
2361 if (RT_FAILURE (vrc))
2362 {
2363 lastAccessError = Utf8StrFmt (
2364 tr ("Could not get the storage format of the hard disk "
2365 "'%ls'%s"), m.locationFull.raw(), vdError (vrc).raw());
2366 throw S_OK;
2367 }
2368
2369 ComAssertThrow (backendName != NULL, E_FAIL);
2370
2371 unconst (mm.format) = backendName;
2372 RTStrFree (backendName);
2373 }
2374
2375 PVBOXHDD hdd;
2376 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
2377 ComAssertRCThrow (vrc, E_FAIL);
2378
2379 try
2380 {
2381 vrc = VDOpen (hdd, Utf8Str (mm.format), location,
2382 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
2383 NULL);
2384 if (RT_FAILURE (vrc))
2385 {
2386 lastAccessError = Utf8StrFmt (
2387 tr ("Could not open the hard disk '%ls'%s"),
2388 m.locationFull.raw(), vdError (vrc).raw());
2389 throw S_OK;
2390 }
2391
2392 /* check the UUID */
2393 RTUUID uuid;
2394 vrc = VDGetUuid (hdd, 0, &uuid);
2395 ComAssertRCThrow (vrc, E_FAIL);
2396
2397 if (isNew)
2398 {
2399 unconst (m.id) = uuid;
2400 }
2401 else
2402 {
2403 if (m.id != uuid)
2404 {
2405 lastAccessError = Utf8StrFmt (
2406 tr ("UUID {%RTuuid} of the hard disk '%ls' "
2407 "does not match the value {%RTuuid} stored in the "
2408 "media registry ('%ls')"),
2409 &uuid, m.locationFull.raw(), m.id.raw(),
2410 mVirtualBox->settingsFileName().raw());
2411 throw S_OK;
2412 }
2413 }
2414
2415 /* check the type */
2416 VDIMAGETYPE type;
2417 vrc = VDGetImageType (hdd, 0, &type);
2418 ComAssertRCThrow (vrc, E_FAIL);
2419
2420 if (type == VD_IMAGE_TYPE_DIFF)
2421 {
2422 vrc = VDGetParentUuid (hdd, 0, &uuid);
2423 ComAssertRCThrow (vrc, E_FAIL);
2424
2425 if (isNew)
2426 {
2427 /* the parent must be known to us. Note that we freely
2428 * call locking methods of mVirtualBox and parent from the
2429 * write lock (breaking the {parent,child} lock order)
2430 * because there may be no concurrent access to the just
2431 * opened hard disk on ther threads yet (and init() will
2432 * fail if this method reporst MediaState_Inaccessible) */
2433
2434 Guid id = uuid;
2435 ComObjPtr <HardDisk2> parent;
2436 rc = mVirtualBox->findHardDisk2 (&id, NULL,
2437 false /* aSetError */,
2438 &parent);
2439 if (FAILED (rc))
2440 {
2441 lastAccessError = Utf8StrFmt (
2442 tr ("Parent hard disk with UUID {%RTuuid} of the "
2443 "hard disk '%ls' is not found in the media "
2444 "registry ('%ls')"),
2445 &uuid, m.locationFull.raw(),
2446 mVirtualBox->settingsFileName().raw());
2447 throw S_OK;
2448 }
2449
2450 /* deassociate from VirtualBox, associate with parent */
2451
2452 mVirtualBox->removeDependentChild (this);
2453
2454 /* we set mParent & children() */
2455 AutoWriteLock treeLock (this->treeLock());
2456
2457 Assert (mParent.isNull());
2458 mParent = parent;
2459 mParent->addDependentChild (this);
2460 }
2461 else
2462 {
2463 /* we access mParent */
2464 AutoReadLock treeLock (this->treeLock());
2465
2466 /* check that parent UUIDs match. Note that there's no need
2467 * for the parent's AutoCaller (our lifetime is bound to
2468 * it) */
2469
2470 if (mParent.isNull())
2471 {
2472 lastAccessError = Utf8StrFmt (
2473 tr ("Hard disk '%ls' is differencing but it is not "
2474 "associated with any parent hard disk in the "
2475 "media registry ('%ls')"),
2476 m.locationFull.raw(),
2477 mVirtualBox->settingsFileName().raw());
2478 throw S_OK;
2479 }
2480
2481 AutoReadLock parentLock (mParent);
2482 if (mParent->state() != MediaState_Inaccessible &&
2483 mParent->id() != uuid)
2484 {
2485 lastAccessError = Utf8StrFmt (
2486 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' "
2487 "does not match UUID {%RTuuid} of its parent "
2488 "hard disk stored in the media registry ('%ls')"),
2489 &uuid, m.locationFull.raw(),
2490 mParent->id().raw(),
2491 mVirtualBox->settingsFileName().raw());
2492 throw S_OK;
2493 }
2494
2495 /// @todo NEWMEDIA what to do if the parent is not
2496 /// accessible while the diff is? Probably, nothing. The
2497 /// real code will detect the mismatch anyway.
2498 }
2499 }
2500
2501 m.size = VDGetFileSize (hdd, 0);
2502 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
2503
2504 success = true;
2505 }
2506 catch (HRESULT aRC)
2507 {
2508 rc = aRC;
2509 }
2510
2511 VDDestroy (hdd);
2512
2513 }
2514 catch (HRESULT aRC)
2515 {
2516 rc = aRC;
2517 }
2518
2519 alock.enter();
2520
2521 /* inform other callers if there are any */
2522 if (m.queryInfoCallers > 0)
2523 {
2524 RTSemEventMultiSignal (m.queryInfoSem);
2525 }
2526 else
2527 {
2528 /* delete the semaphore ourselves */
2529 RTSemEventMultiDestroy (m.queryInfoSem);
2530 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2531 }
2532
2533 /* Restore the proper state when appropriate. Keep in mind that LockedRead
2534 * and LockedWrite are not transitable to Inaccessible. */
2535 if (success)
2536 {
2537 if (tempStateSet)
2538 m.state = MediaState_Created;
2539 m.lastAccessError.setNull();
2540 }
2541 else
2542 {
2543 if (tempStateSet)
2544 m.state = MediaState_Inaccessible;
2545 m.lastAccessError = lastAccessError;
2546
2547 LogWarningFunc (("'%ls' is not accessible (error='%ls', "
2548 "rc=%Rhrc, vrc=%Rrc)\n",
2549 m.locationFull.raw(), m.lastAccessError.raw(),
2550 rc, vrc));
2551 }
2552
2553 return rc;
2554}
2555
2556/**
2557 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
2558 * write lock.
2559 *
2560 * @note Locks treeLock() for reading.
2561 */
2562HRESULT HardDisk2::canClose()
2563{
2564 /* we access children */
2565 AutoReadLock treeLock (this->treeLock());
2566
2567 if (children().size() != 0)
2568 return setError (E_FAIL,
2569 tr ("Hard disk '%ls' has %d child hard disks"),
2570 children().size());
2571
2572 return S_OK;
2573}
2574
2575/**
2576 * @note Called from within this object's AutoWriteLock.
2577 */
2578HRESULT HardDisk2::canAttach (const Guid &aMachineId,
2579 const Guid &aSnapshotId)
2580{
2581 if (mm.numCreateDiffTasks > 0)
2582 return setError (E_FAIL,
2583 tr ("One or more differencing child hard disks are "
2584 "being created for the hard disk '%ls' (%u)"),
2585 m.locationFull.raw(), mm.numCreateDiffTasks);
2586
2587 return S_OK;
2588}
2589
2590/**
2591 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
2592 * from under mVirtualBox write lock.
2593 *
2594 * @note Locks treeLock() for writing.
2595 */
2596HRESULT HardDisk2::unregisterWithVirtualBox()
2597{
2598 /* Note that we need to de-associate ourselves from the parent to let
2599 * unregisterHardDisk2() properly save the registry */
2600
2601 /* we modify mParent and access children */
2602 AutoWriteLock treeLock (this->treeLock());
2603
2604 const ComObjPtr <HardDisk2, ComWeakRef> parent = mParent;
2605
2606 AssertReturn (children().size() == 0, E_FAIL);
2607
2608 if (!mParent.isNull())
2609 {
2610 /* deassociate from the parent, associate with VirtualBox */
2611 mVirtualBox->addDependentChild (this);
2612 mParent->removeDependentChild (this);
2613 mParent.setNull();
2614 }
2615
2616 HRESULT rc = mVirtualBox->unregisterHardDisk2 (this);
2617
2618 if (FAILED (rc))
2619 {
2620 if (!parent.isNull())
2621 {
2622 /* re-associate with the parent as we are still relatives in the
2623 * registry */
2624 mParent = parent;
2625 mParent->addDependentChild (this);
2626 mVirtualBox->removeDependentChild (this);
2627 }
2628 }
2629
2630 return rc;
2631}
2632
2633/**
2634 * Returns the last error message collected by the vdErrorCall callback and
2635 * resets it.
2636 *
2637 * The error message is returned prepended with a dot and a space, like this:
2638 * <code>
2639 * ". <error_text> (%Rrc)"
2640 * </code>
2641 * to make it easily appendable to a more general error message. The @c %Rrc
2642 * format string is given @a aVRC as an argument.
2643 *
2644 * If there is no last error message collected by vdErrorCall or if it is a
2645 * null or empty string, then this function returns the following text:
2646 * <code>
2647 * " (%Rrc)"
2648 * </code>
2649 *
2650 * @note Doesn't do any object locking; it is assumed that the caller makes sure
2651 * the callback isn't called by more than one thread at a time.
2652 *
2653 * @param aVRC VBox error code to use when no error message is provided.
2654 */
2655Utf8Str HardDisk2::vdError (int aVRC)
2656{
2657 Utf8Str error;
2658
2659 if (mm.vdError.isEmpty())
2660 error = Utf8StrFmt (" (%Rrc)", aVRC);
2661 else
2662 error = Utf8StrFmt (". %s (%Rrc)", mm.vdError.raw(), aVRC);
2663
2664 mm.vdError.setNull();
2665
2666 return error;
2667}
2668
2669/**
2670 * Error message callback.
2671 *
2672 * Puts the reported error message to the mm.vdError field.
2673 *
2674 * @note Doesn't do any object locking; it is assumed that the caller makes sure
2675 * the callback isn't called by more than one thread at a time.
2676 *
2677 * @param pvUser The opaque data passed on container creation.
2678 * @param rc The VBox error code.
2679 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
2680 * @param pszFormat Error message format string.
2681 * @param va Error message arguments.
2682 */
2683/*static*/
2684DECLCALLBACK(void) HardDisk2::vdErrorCall (void *pvUser, int rc, RT_SRC_POS_DECL,
2685 const char *pszFormat, va_list va)
2686{
2687 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
2688 AssertReturnVoid (that != NULL);
2689
2690 that->mm.vdError = Utf8StrFmtVA (pszFormat, va);
2691}
2692
2693/**
2694 * PFNVMPROGRESS callback handler for Task operations.
2695 *
2696 * @param uPercent Completetion precentage (0-100).
2697 * @param pvUser Pointer to the Progress instance.
2698 */
2699/*static*/
2700DECLCALLBACK(int) HardDisk2::vdProgressCall (PVM /* pVM */, unsigned uPercent,
2701 void *pvUser)
2702{
2703 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
2704 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
2705
2706 if (that->mm.vdProgress != NULL)
2707 {
2708 /* update the progress object, capping it at 99% as the final percent
2709 * is used for additional operations like setting the UUIDs and similar. */
2710 that->mm.vdProgress->notifyProgress (RT_MIN (uPercent, 99));
2711 }
2712
2713 return VINF_SUCCESS;
2714}
2715
2716/**
2717 * Thread function for time-consuming tasks.
2718 *
2719 * The Task structure passed to @a pvUser must be allocated using new and will
2720 * be freed by this method before it returns.
2721 *
2722 * @param pvUser Pointer to the Task instance.
2723 */
2724/* static */
2725DECLCALLBACK(int) HardDisk2::taskThread (RTTHREAD thread, void *pvUser)
2726{
2727 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
2728 AssertReturn (task.get(), VERR_GENERAL_FAILURE);
2729
2730 bool isAsync = thread != NIL_RTTHREAD;
2731
2732 HardDisk2 *that = task->that;
2733
2734 /// @todo ugly hack, fix ComAssert... later
2735 #define setError that->setError
2736
2737 /* Note: no need in AutoCaller because Task does that */
2738
2739 LogFlowFuncEnter();
2740 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
2741
2742 HRESULT rc = S_OK;
2743
2744 switch (task->operation)
2745 {
2746 ////////////////////////////////////////////////////////////////////////
2747
2748 case Task::CreateDynamic:
2749 case Task::CreateFixed:
2750 {
2751 /* The lock is also used as a signal from the task initiator (which
2752 * releases it only after RTThreadCreate()) that we can start the job */
2753 AutoWriteLock thatLock (that);
2754
2755 /* these parameters we need after creation */
2756 RTUUID uuid;
2757 uint64_t size = 0, logicalSize = 0;
2758
2759 try
2760 {
2761 PVBOXHDD hdd;
2762 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
2763 ComAssertRCThrow (vrc, E_FAIL);
2764
2765 Utf8Str format (that->mm.format);
2766 Utf8Str location (that->m.locationFull);
2767
2768 /* unlock before the potentially lengthy operation */
2769 Assert (that->m.state == MediaState_Creating);
2770 thatLock.leave();
2771
2772 try
2773 {
2774 /* ensure the directory exists */
2775 rc = VirtualBox::ensureFilePathExists (location);
2776 CheckComRCThrowRC (rc);
2777
2778 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
2779
2780 /* needed for vdProgressCallback */
2781 that->mm.vdProgress = task->progress;
2782
2783 vrc = VDCreateBase (hdd, format, location,
2784 task->operation == Task::CreateDynamic ?
2785 VD_IMAGE_TYPE_NORMAL :
2786 VD_IMAGE_TYPE_FIXED,
2787 task->d.size * _1M,
2788 VD_IMAGE_FLAGS_NONE,
2789 NULL, &geo, &geo, NULL,
2790 VD_OPEN_FLAGS_NORMAL,
2791 NULL, that->mm.vdDiskIfaces);
2792
2793 if (RT_FAILURE (vrc))
2794 {
2795 throw setError (E_FAIL,
2796 tr ("Could not create the hard disk storage "
2797 "unit '%s'%s"),
2798 location.raw(), that->vdError (vrc).raw());
2799 }
2800
2801 vrc = VDGetUuid (hdd, 0, &uuid);
2802 ComAssertRCThrow (vrc, E_FAIL);
2803
2804 size = VDGetFileSize (hdd, 0);
2805 logicalSize = VDGetSize (hdd, 0) / _1M;
2806 }
2807 catch (HRESULT aRC) { rc = aRC; }
2808
2809 VDDestroy (hdd);
2810 }
2811 catch (HRESULT aRC) { rc = aRC; }
2812
2813 if (SUCCEEDED (rc))
2814 {
2815 /* mVirtualBox->registerHardDisk2() needs a write lock */
2816 AutoWriteLock vboxLock (that->mVirtualBox);
2817 thatLock.enter();
2818
2819 unconst (that->m.id) = uuid;
2820
2821 that->m.size = size;
2822 that->mm.logicalSize = logicalSize;
2823
2824 /* register with mVirtualBox as the last step and move to
2825 * Created state only on success (leaving an orphan file is
2826 * better than breaking media registry consistency) */
2827 rc = that->mVirtualBox->registerHardDisk2 (that);
2828
2829 if (SUCCEEDED (rc))
2830 that->m.state = MediaState_Created;
2831 }
2832
2833 if (FAILED (rc))
2834 {
2835 thatLock.maybeEnter();
2836
2837 /* back to NotCreated on failiure */
2838 that->m.state = MediaState_NotCreated;
2839 }
2840
2841 break;
2842 }
2843
2844 ////////////////////////////////////////////////////////////////////////
2845
2846 case Task::CreateDiff:
2847 {
2848 ComObjPtr <HardDisk2> &target = task->d.target;
2849
2850 /* Lock both in {parent,child} order. The lock is also used as a
2851 * signal from the task initiator (which releases it only after
2852 * RTThreadCreate()) that we can start the job*/
2853 AutoMultiWriteLock2 thatLock (that, target);
2854
2855 uint64_t size = 0, logicalSize = 0;
2856
2857 try
2858 {
2859 PVBOXHDD hdd;
2860 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
2861 ComAssertRCThrow (vrc, E_FAIL);
2862
2863 Utf8Str format (that->mm.format);
2864 Utf8Str location (that->m.locationFull);
2865
2866 Utf8Str targetFormat (target->mm.format);
2867 Utf8Str targetLocation (target->m.locationFull);
2868 Guid targetId = target->m.id;
2869
2870 /* UUID must have been set by setLocation() */
2871 Assert (!targetId.isEmpty());
2872
2873 Assert (target->m.state == MediaState_Creating);
2874
2875 /* Note: MediaState_LockedWrite is ok when taking an online
2876 * snapshot */
2877 Assert (that->m.state == MediaState_LockedRead ||
2878 that->m.state == MediaState_LockedWrite);
2879
2880 /* unlock before the potentially lengthy operation */
2881 thatLock.leave();
2882
2883 try
2884 {
2885 vrc = VDOpen (hdd, format, location,
2886 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
2887 NULL);
2888 if (RT_FAILURE (vrc))
2889 {
2890 throw setError (E_FAIL,
2891 tr ("Could not open the hard disk storage "
2892 "unit '%s'%s"),
2893 location.raw(), that->vdError (vrc).raw());
2894 }
2895
2896 /* ensure the target directory exists */
2897 rc = VirtualBox::ensureFilePathExists (targetLocation);
2898 CheckComRCThrowRC (rc);
2899
2900 /* needed for vdProgressCallback */
2901 that->mm.vdProgress = task->progress;
2902
2903 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
2904 VD_IMAGE_FLAGS_NONE,
2905 NULL, targetId.raw(),
2906 VD_OPEN_FLAGS_NORMAL,
2907 NULL, that->mm.vdDiskIfaces);
2908
2909 that->mm.vdProgress = NULL;
2910
2911 if (RT_FAILURE (vrc))
2912 {
2913 throw setError (E_FAIL,
2914 tr ("Could not create the differencing hard disk "
2915 "storage unit '%s'%s"),
2916 targetLocation.raw(), that->vdError (vrc).raw());
2917 }
2918
2919 size = VDGetFileSize (hdd, 0);
2920 logicalSize = VDGetSize (hdd, 0) / _1M;
2921 }
2922 catch (HRESULT aRC) { rc = aRC; }
2923
2924 VDDestroy (hdd);
2925 }
2926 catch (HRESULT aRC) { rc = aRC; }
2927
2928 if (SUCCEEDED (rc))
2929 {
2930 /* we set mParent & children() (note that thatLock is released
2931 * here), but lock VirtualBox first to follow the rule */
2932 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
2933 that->treeLock());
2934
2935 Assert (target->mParent.isNull());
2936
2937 /* associate the child with the parent and deassociate from
2938 * VirtualBox */
2939 target->mParent = that;
2940 that->addDependentChild (target);
2941 target->mVirtualBox->removeDependentChild (target);
2942
2943 /* register with mVirtualBox as the last step and move to
2944 * Created state only on success (leaving an orphan file is
2945 * better than breaking media registry consistency) */
2946 rc = that->mVirtualBox->registerHardDisk2 (target);
2947
2948 if (FAILED (rc))
2949 {
2950 /* break the parent association on failure to register */
2951 target->mVirtualBox->addDependentChild (target);
2952 that->removeDependentChild (target);
2953 target->mParent.setNull();
2954 }
2955 }
2956
2957 thatLock.maybeEnter();
2958
2959 if (SUCCEEDED (rc))
2960 {
2961 target->m.state = MediaState_Created;
2962
2963 target->m.size = size;
2964 target->mm.logicalSize = logicalSize;
2965 }
2966 else
2967 {
2968 /* back to NotCreated on failiure */
2969 target->m.state = MediaState_NotCreated;
2970 }
2971
2972 if (isAsync)
2973 {
2974 /* unlock ourselves when done (unless in MediaState_LockedWrite
2975 * state because of taking the online snapshot*/
2976 if (that->m.state != MediaState_LockedWrite)
2977 {
2978 HRESULT rc2 = that->UnlockRead (NULL);
2979 AssertComRC (rc2);
2980 }
2981 }
2982
2983 /* deregister the task registered in createDiffStorage() */
2984 Assert (that->mm.numCreateDiffTasks != 0);
2985 -- that->mm.numCreateDiffTasks;
2986
2987 /* Note that in sync mode, it's the caller's responsibility to
2988 * unlock the hard disk */
2989
2990 break;
2991 }
2992
2993 ////////////////////////////////////////////////////////////////////////
2994
2995 case Task::Merge:
2996 {
2997 /* The lock is also used as a signal from the task initiator (which
2998 * releases it only after RTThreadCreate()) that we can start the
2999 * job. We don't actually need the lock for anything else since the
3000 * object is protected by MediaState_Deleting and we don't modify
3001 * its sensitive fields below */
3002 {
3003 AutoWriteLock thatLock (that);
3004 }
3005
3006 MergeChain *chain = task->d.chain.get();
3007
3008#if 1
3009 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
3010#endif
3011
3012 try
3013 {
3014 PVBOXHDD hdd;
3015 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3016 ComAssertRCThrow (vrc, E_FAIL);
3017
3018 try
3019 {
3020 /* open all hard disks in the chain (they are in the
3021 * {parent,child} order in there. Note that we don't lock
3022 * objects in this chain since they must be in states
3023 * (Deleting and LockedWrite) that prevent from chaning
3024 * their format and location fields from outside. */
3025
3026 for (MergeChain::const_iterator it = chain->begin();
3027 it != chain->end(); ++ it)
3028 {
3029 /* complex sanity (sane complexity) */
3030 Assert ((chain->isForward() &&
3031 ((*it != chain->back() &&
3032 (*it)->m.state == MediaState_Deleting) ||
3033 (*it == chain->back() &&
3034 (*it)->m.state == MediaState_LockedWrite))) ||
3035 (!chain->isForward() &&
3036 ((*it != chain->front() &&
3037 (*it)->m.state == MediaState_Deleting) ||
3038 (*it == chain->front() &&
3039 (*it)->m.state == MediaState_LockedWrite))));
3040
3041 Assert (*it == chain->target() ||
3042 (*it)->m.backRefs.size() == 0);
3043
3044 /* open the first image with VDOPEN_FLAGS_INFO because
3045 * it's not necessarily the base one */
3046 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3047 Utf8Str ((*it)->m.locationFull),
3048 it == chain->begin() ?
3049 VD_OPEN_FLAGS_INFO : 0,
3050 NULL);
3051 if (RT_FAILURE (vrc))
3052 throw vrc;
3053#if 1
3054 LogFlow (("*** MERGE disk = %ls\n",
3055 (*it)->m.locationFull.raw()));
3056#endif
3057 }
3058
3059 /* needed for vdProgressCallback */
3060 that->mm.vdProgress = task->progress;
3061
3062 unsigned start = chain->isForward() ?
3063 0 : chain->size() - 1;
3064 unsigned end = chain->isForward() ?
3065 chain->size() - 1 : 0;
3066#if 1
3067 LogFlow (("*** MERGE from %d to %d\n", start, end));
3068#endif
3069 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
3070
3071 that->mm.vdProgress = NULL;
3072
3073 if (RT_FAILURE (vrc))
3074 throw vrc;
3075
3076 /* update parent UUIDs */
3077 /// @todo VDMerge should be taught to do so, including the
3078 /// multiple children case
3079 if (chain->isForward())
3080 {
3081 /* target's UUID needs to be updated (note that target
3082 * is the only image in the container on success) */
3083 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
3084 if (RT_FAILURE (vrc))
3085 throw vrc;
3086 }
3087 else
3088 {
3089 /* we need to update UUIDs of all source's children
3090 * which cannot be part of the container at once so
3091 * add each one in there individually */
3092 if (chain->children().size() > 0)
3093 {
3094 for (List::const_iterator it = chain->children().begin();
3095 it != chain->children().end(); ++ it)
3096 {
3097 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
3098 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3099 Utf8Str ((*it)->m.locationFull),
3100 VD_OPEN_FLAGS_INFO, NULL);
3101 if (RT_FAILURE (vrc))
3102 throw vrc;
3103
3104 vrc = VDSetParentUuid (hdd, 1,
3105 chain->target()->m.id);
3106 if (RT_FAILURE (vrc))
3107 throw vrc;
3108
3109 vrc = VDClose (hdd, false /* fDelete */);
3110 if (RT_FAILURE (vrc))
3111 throw vrc;
3112 }
3113 }
3114 }
3115 }
3116 catch (HRESULT aRC) { rc = aRC; }
3117 catch (int aVRC)
3118 {
3119 throw setError (E_FAIL,
3120 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
3121 chain->source()->m.locationFull.raw(),
3122 chain->target()->m.locationFull.raw(),
3123 that->vdError (aVRC).raw());
3124 }
3125
3126 VDDestroy (hdd);
3127 }
3128 catch (HRESULT aRC) { rc = aRC; }
3129
3130 HRESULT rc2;
3131
3132 bool saveSettingsFailed = false;
3133
3134 if (SUCCEEDED (rc))
3135 {
3136 /* all hard disks but the target were successfully deleted by
3137 * VDMerge; reparent the last one and uninitialize deleted */
3138
3139 /* we set mParent & children() (note that thatLock is released
3140 * here), but lock VirtualBox first to follow the rule */
3141 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3142 that->treeLock());
3143
3144 HardDisk2 *source = chain->source();
3145 HardDisk2 *target = chain->target();
3146
3147 if (chain->isForward())
3148 {
3149 /* first, unregister the target since it may become a base
3150 * hard disk which needs re-registration */
3151 rc2 = target->mVirtualBox->
3152 unregisterHardDisk2 (target, false /* aSaveSettings */);
3153 AssertComRC (rc2);
3154
3155 /* then, reparent it and disconnect the deleted branch at
3156 * both ends (chain->parent() is source's parent) */
3157 target->mParent->removeDependentChild (target);
3158 target->mParent = chain->parent();
3159 if (!target->mParent.isNull())
3160 {
3161 target->mParent->addDependentChild (target);
3162 target->mParent->removeDependentChild (source);
3163 source->mParent.setNull();
3164 }
3165 else
3166 {
3167 target->mVirtualBox->addDependentChild (target);
3168 target->mVirtualBox->removeDependentChild (source);
3169 }
3170
3171 /* then, register again */
3172 rc2 = target->mVirtualBox->
3173 registerHardDisk2 (target, false /* aSaveSettings */);
3174 AssertComRC (rc2);
3175 }
3176 else
3177 {
3178 Assert (target->children().size() == 1);
3179 HardDisk2 *targetChild = target->children().front();
3180
3181 /* disconnect the deleted branch at the elder end */
3182 target->removeDependentChild (targetChild);
3183 targetChild->mParent.setNull();
3184
3185 const List &children = chain->children();
3186
3187 /* reparent source's chidren and disconnect the deleted
3188 * branch at the younger end m*/
3189 if (children.size() > 0)
3190 {
3191 /* obey {parent,child} lock order */
3192 AutoWriteLock sourceLock (source);
3193
3194 for (List::const_iterator it = children.begin();
3195 it != children.end(); ++ it)
3196 {
3197 AutoWriteLock childLock (*it);
3198
3199 (*it)->mParent = target;
3200 (*it)->mParent->addDependentChild (*it);
3201 source->removeDependentChild (*it);
3202 }
3203 }
3204 }
3205
3206 /* try to save the hard disk registry */
3207 rc = that->mVirtualBox->saveSettings();
3208
3209 if (SUCCEEDED (rc))
3210 {
3211 /* unregister and uninitialize all hard disks in the chain
3212 * but the target */
3213
3214 for (MergeChain::iterator it = chain->begin();
3215 it != chain->end();)
3216 {
3217 if (*it == chain->target())
3218 {
3219 ++ it;
3220 continue;
3221 }
3222
3223 rc2 = (*it)->mVirtualBox->
3224 unregisterHardDisk2 (*it, false /* aSaveSettings */);
3225 AssertComRC (rc2);
3226
3227 /* now, uninitialize the deleted hard disk (note that
3228 * due to the Deleting state, uninit() will not touch
3229 * the parent-child relationship so we need to
3230 * uninitialize each disk individually) */
3231
3232 /* note that the operation initiator hard disk (which is
3233 * normally also the source hard disk) is a special case
3234 * -- there is one more caller added by Task to it which
3235 * we must release. Also, if we are in sync mode, the
3236 * caller may still hold an AutoCaller instance for it
3237 * and therefore we cannot uninit() it (it's therefore
3238 * the caller's responsibility) */
3239 if (*it == that)
3240 task->autoCaller.release();
3241
3242 /* release the caller added by MergeChain before
3243 * uninit() */
3244 (*it)->releaseCaller();
3245
3246 if (isAsync || *it != that)
3247 (*it)->uninit();
3248
3249 /* delete (to prevent uninitialization in MergeChain
3250 * dtor) and advance to the next item */
3251 it = chain->erase (it);
3252 }
3253
3254 /* Note that states of all other hard disks (target, parent,
3255 * children) will be restored by the MergeChain dtor */
3256 }
3257 else
3258 {
3259 /* too bad if we fail, but we'll need to rollback everything
3260 * we did above to at least keep the the HD tree in sync
3261 * with the current regstry on disk */
3262
3263 saveSettingsFailed = true;
3264
3265 /// @todo NEWMEDIA implement a proper undo
3266
3267 AssertFailed();
3268 }
3269 }
3270
3271 if (FAILED (rc))
3272 {
3273 /* Here we come if either VDMerge() failed (in which case we
3274 * assume that it tried to do everything to make a further
3275 * retry possible -- e.g. not deleted intermediate hard disks
3276 * and so on) or VirtualBox::saveSettings() failed (where we
3277 * should have the original tree but with intermediate storage
3278 * units deleted by VDMerge()). We have to only restore states
3279 * (through the MergeChain dtor) unless we are run syncronously
3280 * in which case it's the responsibility of the caller as stated
3281 * in the mergeTo() docs. The latter also implies that we
3282 * don't own the merge chain, so release it in this case. */
3283
3284 if (!isAsync)
3285 task->d.chain.release();
3286
3287 NOREF (saveSettingsFailed);
3288 }
3289
3290 break;
3291 }
3292
3293 ////////////////////////////////////////////////////////////////////////
3294
3295 case Task::Delete:
3296 {
3297 /* The lock is also used as a signal from the task initiator (which
3298 * releases it only after RTThreadCreate()) that we can start the job */
3299 AutoWriteLock thatLock (that);
3300
3301 try
3302 {
3303 PVBOXHDD hdd;
3304 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3305 ComAssertRCThrow (vrc, E_FAIL);
3306
3307 Utf8Str format (that->mm.format);
3308 Utf8Str location (that->m.locationFull);
3309
3310 /* unlock before the potentially lengthy operation */
3311 Assert (that->m.state == MediaState_Deleting);
3312 thatLock.leave();
3313
3314 try
3315 {
3316 vrc = VDOpen (hdd, format, location,
3317 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3318 NULL);
3319 if (RT_SUCCESS (vrc))
3320 vrc = VDClose (hdd, true /* fDelete */);
3321
3322 if (RT_FAILURE (vrc))
3323 {
3324 throw setError (E_FAIL,
3325 tr ("Could not delete the hard disk storage "
3326 "unit '%s'%s"),
3327 location.raw(), that->vdError (vrc).raw());
3328 }
3329
3330 }
3331 catch (HRESULT aRC) { rc = aRC; }
3332
3333 VDDestroy (hdd);
3334 }
3335 catch (HRESULT aRC) { rc = aRC; }
3336
3337 thatLock.maybeEnter();
3338
3339 /* go to the NotCreated state even on failure since the storage
3340 * may have been already partially deleted and cannot be used any
3341 * more. One will be able to manually re-open the storage if really
3342 * needed to re-register it. */
3343 that->m.state = MediaState_NotCreated;
3344
3345 break;
3346 }
3347
3348 default:
3349 AssertFailedReturn (VERR_GENERAL_FAILURE);
3350 }
3351
3352 /* complete the progress if run asynchronously */
3353 if (isAsync)
3354 {
3355 if (!task->progress.isNull())
3356 task->progress->notifyComplete (rc);
3357 }
3358 else
3359 {
3360 task->rc = rc;
3361 }
3362
3363 LogFlowFunc (("rc=%Rhrc\n", rc));
3364 LogFlowFuncLeave();
3365
3366 return VINF_SUCCESS;
3367
3368 /// @todo ugly hack, fix ComAssert... later
3369 #undef setError
3370}
3371
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette