VirtualBox

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

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

s/return E_NOTIMPL/ReturnComNotImplemented()/g Main/*.cpp
as suggested by dmik (except for Performance.cpp which is
a different beast entirely for the tr/setError namespace).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 116.1 KB
Line 
1/* $Id: HardDisk2Impl.cpp 14715 2008-11-27 15:34:57Z 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
Note: See TracBrowser for help on using the repository browser.

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