VirtualBox

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

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

Main: Treat hard disks with no Create capabilities as Created right after IVirtualBox::createHardDisk2() and don't allow IHardDisk2::create*Storage()/deleteStorage() for them.

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

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