VirtualBox

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

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

corrected a couple of typos

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