VirtualBox

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

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

Main: Use the parent's hard disk format when implicitly creating differencing hard disks (or the default hard disk format if the parent format doesn't support differencing).

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

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