VirtualBox

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

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

Revive VBOX_E_OBJECT_IN_USE for deleteStorage as discussed with dmik.

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