VirtualBox

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

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

Added vim modelines to aid following coding guidelines, like no tabs,
similar to what is already in the xidl file.

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

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