VirtualBox

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

Last change on this file since 15498 was 15486, checked in by vboxsync, 16 years ago

API: add IHardDisk2::Compact, at the moment just a stub returning not implemented.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 125.0 KB
Line 
1/* $Id: HardDisk2Impl.cpp 15486 2008-12-15 09:57:20Z 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, CBSTR aFormat,
510 CBSTR 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, CBSTR 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.isEmpty());
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 m.lastAccessError = tr ("Accessibility check was not yet performed");
680
681 /* required */
682 unconst (m.id) = aNode.value <Guid> ("uuid");
683
684 /* optional */
685 {
686 settings::Key descNode = aNode.findKey ("Description");
687 if (!descNode.isNull())
688 m.description = descNode.keyStringValue();
689 }
690
691 /* required */
692 Bstr format = aNode.stringValue ("format");
693 AssertReturn (!format.isNull(), E_FAIL);
694 rc = setFormat (format);
695 CheckComRCReturnRC (rc);
696
697 /* properties (after setting the format as it populates the map). Note that
698 * if some properties are not supported but preseint in the settings file,
699 * they will still be read and accessible (for possible backward
700 * compatibility; we can also clean them up from the XML upon next
701 * XML format versino change if we wish) */
702 Key::List properties = aNode.keys ("Property");
703 for (Key::List::const_iterator it = properties.begin();
704 it != properties.end(); ++ it)
705 {
706 mm.properties [Bstr (it->stringValue ("name"))] =
707 Bstr (it->stringValue ("value"));
708 }
709
710 /* required */
711 Bstr location = aNode.stringValue ("location");
712 rc = setLocation (location);
713 CheckComRCReturnRC (rc);
714
715 /* type is only for base hard disks */
716 if (mParent.isNull())
717 {
718 const char *type = aNode.stringValue ("type");
719 if (strcmp (type, "Normal") == 0)
720 mm.type = HardDiskType_Normal;
721 else if (strcmp (type, "Immutable") == 0)
722 mm.type = HardDiskType_Immutable;
723 else if (strcmp (type, "Writethrough") == 0)
724 mm.type = HardDiskType_Writethrough;
725 else
726 AssertFailed();
727 }
728
729 LogFlowThisFunc (("m.locationFull='%ls', mm.format=%ls, m.id={%RTuuid}\n",
730 m.locationFull.raw(), mm.format.raw(), m.id.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 (IN_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 (IN_BSTR aName, IN_BSTR aValue)
1027{
1028 CheckComArgStrNotEmptyOrNull (aName);
1029
1030 AutoCaller autoCaller (this);
1031 CheckComRCReturnRC (autoCaller.rc());
1032
1033 /* VirtualBox::saveSettings() needs a write lock */
1034 AutoMultiWriteLock2 alock (mVirtualBox, this);
1035
1036 switch (m.state)
1037 {
1038 case MediaState_Created:
1039 case MediaState_Inaccessible:
1040 break;
1041 default:
1042 return setStateError();
1043 }
1044
1045 Data::PropertyMap::iterator it = mm.properties.find (Bstr (aName));
1046 if (it == mm.properties.end())
1047 return setError (VBOX_E_OBJECT_NOT_FOUND,
1048 tr ("Property '%ls' does not exist"), aName);
1049
1050 it->second = aValue;
1051
1052 HRESULT rc = mVirtualBox->saveSettings();
1053
1054 return rc;
1055}
1056
1057STDMETHODIMP HardDisk2::GetProperties (IN_BSTR aNames,
1058 ComSafeArrayOut (BSTR, aReturnNames),
1059 ComSafeArrayOut (BSTR, aReturnValues))
1060{
1061 CheckComArgOutSafeArrayPointerValid (aReturnNames);
1062 CheckComArgOutSafeArrayPointerValid (aReturnValues);
1063
1064 AutoCaller autoCaller (this);
1065 CheckComRCReturnRC (autoCaller.rc());
1066
1067 AutoReadLock alock (this);
1068
1069 /// @todo make use of aNames according to the documentation
1070 NOREF (aNames);
1071
1072 com::SafeArray <BSTR> names (mm.properties.size());
1073 com::SafeArray <BSTR> values (mm.properties.size());
1074 size_t i = 0;
1075
1076 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
1077 it != mm.properties.end(); ++ it)
1078 {
1079 it->first.cloneTo (&names [i]);
1080 it->second.cloneTo (&values [i]);
1081 ++ i;
1082 }
1083
1084 names.detachTo (ComSafeArrayOutArg (aReturnNames));
1085 values.detachTo (ComSafeArrayOutArg (aReturnValues));
1086
1087 return S_OK;
1088}
1089
1090STDMETHODIMP HardDisk2::SetProperties (ComSafeArrayIn (IN_BSTR, aNames),
1091 ComSafeArrayIn (IN_BSTR, aValues))
1092{
1093 CheckComArgSafeArrayNotNull (aNames);
1094 CheckComArgSafeArrayNotNull (aValues);
1095
1096 AutoCaller autoCaller (this);
1097 CheckComRCReturnRC (autoCaller.rc());
1098
1099 /* VirtualBox::saveSettings() needs a write lock */
1100 AutoMultiWriteLock2 alock (mVirtualBox, this);
1101
1102 com::SafeArray <IN_BSTR> names (ComSafeArrayInArg (aNames));
1103 com::SafeArray <IN_BSTR> values (ComSafeArrayInArg (aValues));
1104
1105 /* first pass: validate names */
1106 for (size_t i = 0; i < names.size(); ++ i)
1107 {
1108 if (mm.properties.find (Bstr (names [i])) == mm.properties.end())
1109 return setError (VBOX_E_OBJECT_NOT_FOUND,
1110 tr ("Property '%ls' does not exist"), names [i]);
1111 }
1112
1113 /* second pass: assign */
1114 for (size_t i = 0; i < names.size(); ++ i)
1115 {
1116 Data::PropertyMap::iterator it = mm.properties.find (Bstr (names [i]));
1117 AssertReturn (it != mm.properties.end(), E_FAIL);
1118
1119 it->second = values [i];
1120 }
1121
1122 HRESULT rc = mVirtualBox->saveSettings();
1123
1124 return rc;
1125}
1126
1127STDMETHODIMP HardDisk2::CreateDynamicStorage (ULONG64 aLogicalSize,
1128 IProgress **aProgress)
1129{
1130 if (aProgress == NULL)
1131 return E_POINTER;
1132
1133 AutoCaller autoCaller (this);
1134 CheckComRCReturnRC (autoCaller.rc());
1135
1136 AutoWriteLock alock (this);
1137
1138 if (!(mm.formatObj->capabilities() &
1139 HardDiskFormatCapabilities_CreateDynamic))
1140 return setError (VBOX_E_NOT_SUPPORTED,
1141 tr ("Hard disk format '%ls' does not support dynamic storage "
1142 "creation"), mm.format.raw());
1143
1144 switch (m.state)
1145 {
1146 case MediaState_NotCreated:
1147 break;
1148 default:
1149 return setStateError();
1150 }
1151
1152 ComObjPtr <Progress> progress;
1153 progress.createObject();
1154 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1155 BstrFmt (tr ("Creating dynamic hard disk storage unit '%ls'"),
1156 m.location.raw()),
1157 FALSE /* aCancelable */);
1158 CheckComRCReturnRC (rc);
1159
1160 /* setup task object and thread to carry out the operation
1161 * asynchronously */
1162
1163 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDynamic));
1164 AssertComRCReturnRC (task->autoCaller.rc());
1165
1166 task->d.size = aLogicalSize;
1167
1168 rc = task->startThread();
1169 CheckComRCReturnRC (rc);
1170
1171 /* go to Creating state on success */
1172 m.state = MediaState_Creating;
1173
1174 /* task is now owned by taskThread() so release it */
1175 task.release();
1176
1177 /* return progress to the caller */
1178 progress.queryInterfaceTo (aProgress);
1179
1180 return S_OK;
1181}
1182
1183STDMETHODIMP HardDisk2::CreateFixedStorage (ULONG64 aLogicalSize,
1184 IProgress **aProgress)
1185{
1186 if (aProgress == NULL)
1187 return E_POINTER;
1188
1189 AutoCaller autoCaller (this);
1190 CheckComRCReturnRC (autoCaller.rc());
1191
1192 AutoWriteLock alock (this);
1193
1194 if (!(mm.formatObj->capabilities() &
1195 HardDiskFormatCapabilities_CreateFixed))
1196 return setError (VBOX_E_NOT_SUPPORTED,
1197 tr ("Hard disk format '%ls' does not support fixed storage "
1198 "creation"), mm.format.raw());
1199
1200 switch (m.state)
1201 {
1202 case MediaState_NotCreated:
1203 break;
1204 default:
1205 return setStateError();
1206 }
1207
1208 ComObjPtr <Progress> progress;
1209 progress.createObject();
1210 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1211 BstrFmt (tr ("Creating fixed hard disk storage unit '%ls'"),
1212 m.location.raw()),
1213 FALSE /* aCancelable */);
1214 CheckComRCReturnRC (rc);
1215
1216 /* setup task object and thread to carry out the operation
1217 * asynchronously */
1218
1219 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateFixed));
1220 AssertComRCReturnRC (task->autoCaller.rc());
1221
1222 task->d.size = aLogicalSize;
1223
1224 rc = task->startThread();
1225 CheckComRCReturnRC (rc);
1226
1227 /* go to Creating state on success */
1228 m.state = MediaState_Creating;
1229
1230 /* task is now owned by taskThread() so release it */
1231 task.release();
1232
1233 /* return progress to the caller */
1234 progress.queryInterfaceTo (aProgress);
1235
1236 return S_OK;
1237}
1238
1239STDMETHODIMP HardDisk2::DeleteStorage (IProgress **aProgress)
1240{
1241 if (aProgress == NULL)
1242 return E_POINTER;
1243
1244 ComObjPtr <Progress> progress;
1245
1246 HRESULT rc = deleteStorageNoWait (progress);
1247 if (SUCCEEDED (rc))
1248 {
1249 /* return progress to the caller */
1250 progress.queryInterfaceTo (aProgress);
1251 }
1252
1253 return rc;
1254}
1255
1256STDMETHODIMP HardDisk2::CreateDiffStorage (IHardDisk2 *aTarget, IProgress **aProgress)
1257{
1258 if (aTarget == NULL)
1259 return E_INVALIDARG;
1260 if (aProgress == NULL)
1261 return E_POINTER;
1262
1263 AutoCaller autoCaller (this);
1264 CheckComRCReturnRC (autoCaller.rc());
1265
1266 ComObjPtr <HardDisk2> diff;
1267 HRESULT rc = mVirtualBox->cast (aTarget, diff);
1268 CheckComRCReturnRC (rc);
1269
1270 AutoWriteLock alock (this);
1271
1272 if (mm.type == HardDiskType_Writethrough)
1273 return setError (E_FAIL, tr ("Hard disk '%ls' is Writethrough"));
1274
1275 /* We want to be locked for reading as long as our diff child is being
1276 * created */
1277 rc = LockRead (NULL);
1278 CheckComRCReturnRC (rc);
1279
1280 ComObjPtr <Progress> progress;
1281
1282 rc = createDiffStorageNoWait (diff, progress);
1283 if (FAILED (rc))
1284 {
1285 HRESULT rc2 = UnlockRead (NULL);
1286 AssertComRC (rc2);
1287 /* Note: on success, taskThread() will unlock this */
1288 }
1289 else
1290 {
1291 /* return progress to the caller */
1292 progress.queryInterfaceTo (aProgress);
1293 }
1294
1295 return rc;
1296}
1297
1298STDMETHODIMP HardDisk2::MergeTo (IN_GUID aTargetId, IProgress **aProgress)
1299{
1300 AutoCaller autoCaller (this);
1301 CheckComRCReturnRC (autoCaller.rc());
1302
1303 ReturnComNotImplemented();
1304}
1305
1306STDMETHODIMP HardDisk2::CloneTo (IHardDisk2 *aTarget, IProgress **aProgress)
1307{
1308 AutoCaller autoCaller (this);
1309 CheckComRCReturnRC (autoCaller.rc());
1310
1311 ReturnComNotImplemented();
1312}
1313
1314STDMETHODIMP HardDisk2::FlattenTo (IHardDisk2 *aTarget, IProgress **aProgress)
1315{
1316 AutoCaller autoCaller (this);
1317 CheckComRCReturnRC (autoCaller.rc());
1318
1319 ReturnComNotImplemented();
1320}
1321
1322STDMETHODIMP HardDisk2::Compact (IProgress **aProgress)
1323{
1324 AutoCaller autoCaller (this);
1325 CheckComRCReturnRC (autoCaller.rc());
1326
1327 ReturnComNotImplemented();
1328}
1329
1330// public methods for internal purposes only
1331////////////////////////////////////////////////////////////////////////////////
1332
1333/**
1334 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
1335 * of this hard disk or any its child and updates the paths if necessary to
1336 * reflect the new location.
1337 *
1338 * @param aOldPath Old path (full).
1339 * @param aNewPath New path (full).
1340 *
1341 * @note Locks treeLock() for reading, this object and all children for writing.
1342 */
1343void HardDisk2::updatePaths (const char *aOldPath, const char *aNewPath)
1344{
1345 AssertReturnVoid (aOldPath);
1346 AssertReturnVoid (aNewPath);
1347
1348 AutoCaller autoCaller (this);
1349 AssertComRCReturnVoid (autoCaller.rc());
1350
1351 AutoWriteLock alock (this);
1352
1353 /* we access children() */
1354 AutoReadLock treeLock (this->treeLock());
1355
1356 updatePath (aOldPath, aNewPath);
1357
1358 /* update paths of all children */
1359 for (List::const_iterator it = children().begin();
1360 it != children().end();
1361 ++ it)
1362 {
1363 (*it)->updatePaths (aOldPath, aNewPath);
1364 }
1365}
1366
1367/**
1368 * Returns the base hard disk of the hard disk chain this hard disk is part of.
1369 *
1370 * The root hard disk is found by walking up the parent-child relationship axis.
1371 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
1372 * returns itself in response to this method.
1373 *
1374 * @param aLevel Where to store the number of ancestors of this hard disk
1375 * (zero for the root), may be @c NULL.
1376 *
1377 * @note Locks treeLock() for reading.
1378 */
1379ComObjPtr <HardDisk2> HardDisk2::root (uint32_t *aLevel /*= NULL*/)
1380{
1381 ComObjPtr <HardDisk2> root;
1382 uint32_t level;
1383
1384 AutoCaller autoCaller (this);
1385 AssertReturn (autoCaller.isOk(), root);
1386
1387 /* we access mParent */
1388 AutoReadLock treeLock (this->treeLock());
1389
1390 root = this;
1391 level = 0;
1392
1393 if (!mParent.isNull())
1394 {
1395 for (;;)
1396 {
1397 AutoCaller rootCaller (root);
1398 AssertReturn (rootCaller.isOk(), root);
1399
1400 if (root->mParent.isNull())
1401 break;
1402
1403 root = root->mParent;
1404 ++ level;
1405 }
1406 }
1407
1408 if (aLevel != NULL)
1409 *aLevel = level;
1410
1411 return root;
1412}
1413
1414/**
1415 * Returns @c true if this hard disk cannot be modified because it has
1416 * dependants (children) or is part of the snapshot. Related to the hard disk
1417 * type and posterity, not to the current media state.
1418 *
1419 * @note Locks this object and treeLock() for reading.
1420 */
1421bool HardDisk2::isReadOnly()
1422{
1423 AutoCaller autoCaller (this);
1424 AssertComRCReturn (autoCaller.rc(), false);
1425
1426 AutoReadLock alock (this);
1427
1428 /* we access children */
1429 AutoReadLock treeLock (this->treeLock());
1430
1431 switch (mm.type)
1432 {
1433 case HardDiskType_Normal:
1434 {
1435 if (children().size() != 0)
1436 return true;
1437
1438 for (BackRefList::const_iterator it = m.backRefs.begin();
1439 it != m.backRefs.end(); ++ it)
1440 if (it->snapshotIds.size() != 0)
1441 return true;
1442
1443 return false;
1444 }
1445 case HardDiskType_Immutable:
1446 {
1447 return true;
1448 }
1449 case HardDiskType_Writethrough:
1450 {
1451 return false;
1452 }
1453 default:
1454 break;
1455 }
1456
1457 AssertFailedReturn (false);
1458}
1459
1460/**
1461 * Saves hard disk data by appending a new <HardDisk> child node to the given
1462 * parent node which can be either <HardDisks> or <HardDisk>.
1463 *
1464 * @param aaParentNode Parent <HardDisks> or <HardDisk> node.
1465 *
1466 * @note Locks this object, treeLock() and children for reading.
1467 */
1468HRESULT HardDisk2::saveSettings (settings::Key &aParentNode)
1469{
1470 using namespace settings;
1471
1472 AssertReturn (!aParentNode.isNull(), E_FAIL);
1473
1474 AutoCaller autoCaller (this);
1475 CheckComRCReturnRC (autoCaller.rc());
1476
1477 AutoReadLock alock (this);
1478
1479 /* we access mParent */
1480 AutoReadLock treeLock (this->treeLock());
1481
1482 Key diskNode = aParentNode.appendKey ("HardDisk");
1483 /* required */
1484 diskNode.setValue <Guid> ("uuid", m.id);
1485 /* required (note: the original locaiton, not full) */
1486 diskNode.setValue <Bstr> ("location", m.location);
1487 /* required */
1488 diskNode.setValue <Bstr> ("format", mm.format);
1489 /* optional */
1490 if (!m.description.isNull())
1491 {
1492 Key descNode = diskNode.createKey ("Description");
1493 descNode.setKeyValue <Bstr> (m.description);
1494 }
1495
1496 /* optional properties */
1497 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
1498 it != mm.properties.end(); ++ it)
1499 {
1500 /* only save properties that have non-default values */
1501 if (!it->second.isNull())
1502 {
1503 Key propNode = diskNode.appendKey ("Property");
1504 propNode.setValue <Bstr> ("name", it->first);
1505 propNode.setValue <Bstr> ("value", it->second);
1506 }
1507 }
1508
1509 /* only for base hard disks */
1510 if (mParent.isNull())
1511 {
1512 const char *type =
1513 mm.type == HardDiskType_Normal ? "Normal" :
1514 mm.type == HardDiskType_Immutable ? "Immutable" :
1515 mm.type == HardDiskType_Writethrough ? "Writethrough" : NULL;
1516 Assert (type != NULL);
1517 diskNode.setStringValue ("type", type);
1518 }
1519
1520 /* save all children */
1521 for (List::const_iterator it = children().begin();
1522 it != children().end();
1523 ++ it)
1524 {
1525 HRESULT rc = (*it)->saveSettings (diskNode);
1526 AssertComRCReturnRC (rc);
1527 }
1528
1529 return S_OK;
1530}
1531
1532/**
1533 * Compares the location of this hard disk to the given location.
1534 *
1535 * The comparison takes the location details into account. For example, if the
1536 * location is a file in the host's filesystem, a case insensitive comparison
1537 * will be performed for case insensitive filesystems.
1538 *
1539 * @param aLocation Location to compare to (as is).
1540 * @param aResult Where to store the result of comparison: 0 if locations
1541 * are equal, 1 if this object's location is greater than
1542 * the specified location, and -1 otherwise.
1543 */
1544HRESULT HardDisk2::compareLocationTo (const char *aLocation, int &aResult)
1545{
1546 AutoCaller autoCaller (this);
1547 AssertComRCReturnRC (autoCaller.rc());
1548
1549 AutoReadLock alock (this);
1550
1551 Utf8Str locationFull (m.locationFull);
1552
1553 /// @todo NEWMEDIA delegate the comparison to the backend?
1554
1555 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
1556 {
1557 Utf8Str location (aLocation);
1558
1559 /* For locations represented by files, append the default path if
1560 * only the name is given, and then get the full path. */
1561 if (!RTPathHavePath (aLocation))
1562 {
1563 AutoReadLock propsLock (mVirtualBox->systemProperties());
1564 location = Utf8StrFmt ("%ls%c%s",
1565 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
1566 RTPATH_DELIMITER, aLocation);
1567 }
1568
1569 int vrc = mVirtualBox->calculateFullPath (location, location);
1570 if (RT_FAILURE (vrc))
1571 return setError (E_FAIL,
1572 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
1573 location.raw(), vrc);
1574
1575 aResult = RTPathCompare (locationFull, location);
1576 }
1577 else
1578 aResult = locationFull.compare (aLocation);
1579
1580 return S_OK;
1581}
1582
1583/**
1584 * Returns a short version of the location attribute.
1585 *
1586 * Reimplements MediumBase::name() to specially treat non-FS-path locations.
1587 *
1588 * @note Must be called from under this object's read or write lock.
1589 */
1590Utf8Str HardDisk2::name()
1591{
1592 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to requiest
1593 /// this information from the VD backend)
1594
1595 Utf8Str location (m.locationFull);
1596
1597 Utf8Str name = RTPathFilename (location);
1598 return name;
1599}
1600
1601/**
1602 * Checks that this hard disk may be discarded and performs necessary state
1603 * changes.
1604 *
1605 * This method is to be called prior to calling the #discrad() to perform
1606 * necessary consistency checks and place involved hard disks to appropriate
1607 * states. If #discard() is not called or fails, the state modifications
1608 * performed by this method must be undone by #cancelDiscard().
1609 *
1610 * See #discard() for more info about discarding hard disks.
1611 *
1612 * @param aChain Where to store the created merge chain (may return NULL
1613 * if no real merge is necessary).
1614 *
1615 * @note Locks treeLock() for reading. Locks this object, aTarget and all
1616 * intermediate hard disks for writing.
1617 */
1618HRESULT HardDisk2::prepareDiscard (MergeChain * &aChain)
1619{
1620 AutoCaller autoCaller (this);
1621 AssertComRCReturnRC (autoCaller.rc());
1622
1623 aChain = NULL;
1624
1625 AutoWriteLock alock (this);
1626
1627 /* we access mParent & children() */
1628 AutoReadLock treeLock (this->treeLock());
1629
1630 AssertReturn (mm.type == HardDiskType_Normal, E_FAIL);
1631
1632 if (children().size() == 0)
1633 {
1634 /* special treatment of the last hard disk in the chain: */
1635
1636 if (mParent.isNull())
1637 {
1638 /* lock only, to prevent any usage; discard() will unlock */
1639 return LockWrite (NULL);
1640 }
1641
1642 /* the differencing hard disk w/o children will be deleted, protect it
1643 * from attaching to other VMs (this is why Deleting) */
1644
1645 switch (m.state)
1646 {
1647 case MediaState_Created:
1648 m.state = MediaState_Deleting;
1649 break;
1650 default:
1651 return setStateError();
1652 }
1653
1654 /* aChain is intentionally NULL here */
1655
1656 return S_OK;
1657 }
1658
1659 /* not going multi-merge as it's too expensive */
1660 if (children().size() > 1)
1661 return setError (E_FAIL,
1662 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
1663 m.locationFull.raw(), children().size());
1664
1665 /* this is a read-only hard disk with children; it must be associated with
1666 * exactly one snapshot (when the snapshot is being taken, none of the
1667 * current VM's hard disks may be attached to other VMs). Note that by the
1668 * time when discard() is called, there must be no any attachments at all
1669 * (the code calling prepareDiscard() should detach). */
1670 AssertReturn (m.backRefs.size() == 1 &&
1671 !m.backRefs.front().inCurState &&
1672 m.backRefs.front().snapshotIds.size() == 1, E_FAIL);
1673
1674 ComObjPtr <HardDisk2> child = children().front();
1675
1676 /* we keep this locked, so lock the affected child to make sure the lock
1677 * order is correct when calling prepareMergeTo() */
1678 AutoWriteLock childLock (child);
1679
1680 /* delegate the rest to the profi */
1681 if (mParent.isNull())
1682 {
1683 /* base hard disk, backward merge */
1684
1685 Assert (child->m.backRefs.size() == 1);
1686 if (child->m.backRefs.front().machineId != m.backRefs.front().machineId)
1687 {
1688 /* backward merge is too tricky, we'll just detach on discard, so
1689 * lock only, to prevent any usage; discard() will only unlock
1690 * (since we return NULL in aChain) */
1691 return LockWrite (NULL);
1692 }
1693
1694 return child->prepareMergeTo (this, aChain,
1695 true /* aIgnoreAttachments */);
1696 }
1697 else
1698 {
1699 /* forward merge */
1700 return prepareMergeTo (child, aChain,
1701 true /* aIgnoreAttachments */);
1702 }
1703}
1704
1705/**
1706 * Discards this hard disk.
1707 *
1708 * Discarding the hard disk is merging its contents to its differencing child
1709 * hard disk (forward merge) or contents of its child hard disk to itself
1710 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
1711 * a differencing hard disk w/o children, then it will be simply deleted.
1712 * Calling this method on a base hard disk w/o children will do nothing and
1713 * silently succeed. If this hard disk has more than one child, the method will
1714 * currently return an error (since merging in this case would be too expensive
1715 * and result in data duplication).
1716 *
1717 * When the backward merge takes place (i.e. this hard disk is a target) then,
1718 * on success, this hard disk will automatically replace the differencing child
1719 * hard disk used as a source (which will then be deleted) in the attachment
1720 * this child hard disk is associated with. This will happen only if both hard
1721 * disks belong to the same machine because otherwise such a replace would be
1722 * too tricky and could be not expected by the other machine. Same relates to a
1723 * case when the child hard disk is not associated with any machine at all. When
1724 * the backward merge is not applied, the method behaves as if the base hard
1725 * disk were not attached at all -- i.e. simply detaches it from the machine but
1726 * leaves the hard disk chain intact.
1727 *
1728 * This method is basically a wrapper around #mergeTo() that selects the correct
1729 * merge direction and performs additional actions as described above and.
1730 *
1731 * Note that this method will not return until the merge operation is complete
1732 * (which may be quite time consuming depending on the size of the merged hard
1733 * disks).
1734 *
1735 * Note that #prepareDiscard() must be called before calling this method. If
1736 * this method returns a failure, the caller must call #cancelDiscard(). On
1737 * success, #cancelDiscard() must not be called (this method will perform all
1738 * necessary steps such as resetting states of all involved hard disks and
1739 * deleting @a aChain).
1740 *
1741 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1742 * no real merge takes place).
1743 *
1744 * @note Locks the hard disks from the chain for writing. Locks the machine
1745 * object when the backward merge takes place. Locks treeLock() lock for
1746 * reading or writing.
1747 */
1748HRESULT HardDisk2::discard (ComObjPtr <Progress> &aProgress, MergeChain *aChain)
1749{
1750 AssertReturn (!aProgress.isNull(), E_FAIL);
1751
1752 ComObjPtr <HardDisk2> hdFrom;
1753
1754 HRESULT rc = S_OK;
1755
1756 {
1757 AutoCaller autoCaller (this);
1758 AssertComRCReturnRC (autoCaller.rc());
1759
1760 aProgress->advanceOperation (BstrFmt (
1761 tr ("Discarding hard disk '%s'"), name().raw()));
1762
1763 if (aChain == NULL)
1764 {
1765 AutoWriteLock alock (this);
1766
1767 /* we access mParent & children() */
1768 AutoReadLock treeLock (this->treeLock());
1769
1770 Assert (children().size() == 0);
1771
1772 /* special treatment of the last hard disk in the chain: */
1773
1774 if (mParent.isNull())
1775 {
1776 rc = UnlockWrite (NULL);
1777 AssertComRC (rc);
1778 return rc;
1779 }
1780
1781 /* delete the differencing hard disk w/o children */
1782
1783 Assert (m.state == MediaState_Deleting);
1784
1785 /* go back to Created since deleteStorage() expects this state */
1786 m.state = MediaState_Created;
1787
1788 hdFrom = this;
1789
1790 rc = deleteStorageAndWait (&aProgress);
1791 }
1792 else
1793 {
1794 hdFrom = aChain->source();
1795
1796 rc = hdFrom->mergeToAndWait (aChain, &aProgress);
1797 }
1798 }
1799
1800 if (SUCCEEDED (rc))
1801 {
1802 /* mergeToAndWait() cannot uninitialize the initiator because of
1803 * possible AutoCallers on the current thread, deleteStorageAndWait()
1804 * doesn't do it either; do it ourselves */
1805 hdFrom->uninit();
1806 }
1807
1808 return rc;
1809}
1810
1811/**
1812 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
1813 * or fails. Frees memory occupied by @a aChain.
1814 *
1815 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1816 * no real merge takes place).
1817 *
1818 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
1819 * reading.
1820 */
1821void HardDisk2::cancelDiscard (MergeChain *aChain)
1822{
1823 AutoCaller autoCaller (this);
1824 AssertComRCReturnVoid (autoCaller.rc());
1825
1826 if (aChain == NULL)
1827 {
1828 AutoWriteLock alock (this);
1829
1830 /* we access mParent & children() */
1831 AutoReadLock treeLock (this->treeLock());
1832
1833 Assert (children().size() == 0);
1834
1835 /* special treatment of the last hard disk in the chain: */
1836
1837 if (mParent.isNull())
1838 {
1839 HRESULT rc = UnlockWrite (NULL);
1840 AssertComRC (rc);
1841 return;
1842 }
1843
1844 /* the differencing hard disk w/o children will be deleted, protect it
1845 * from attaching to other VMs (this is why Deleting) */
1846
1847 Assert (m.state == MediaState_Deleting);
1848 m.state = MediaState_Created;
1849
1850 return;
1851 }
1852
1853 /* delegate the rest to the profi */
1854 cancelMergeTo (aChain);
1855}
1856
1857/**
1858 * Returns a preferred format for differencing hard disks.
1859 */
1860Bstr HardDisk2::preferredDiffFormat()
1861{
1862 Bstr format;
1863
1864 AutoCaller autoCaller (this);
1865 AssertComRCReturn (autoCaller.rc(), format);
1866
1867 /* mm.format is const, no need to lock */
1868 format = mm.format;
1869
1870 /* check that our own format supports diffs */
1871 if (!(mm.formatObj->capabilities() & HardDiskFormatCapabilities_Differencing))
1872 {
1873 /* use the default format if not */
1874 AutoReadLock propsLock (mVirtualBox->systemProperties());
1875 format = mVirtualBox->systemProperties()->defaultHardDiskFormat();
1876 }
1877
1878 return format;
1879}
1880
1881// protected methods
1882////////////////////////////////////////////////////////////////////////////////
1883
1884/**
1885 * Deletes the hard disk storage unit.
1886 *
1887 * If @a aProgress is not NULL but the object it points to is @c null then a new
1888 * progress object will be created and assigned to @a *aProgress on success,
1889 * otherwise the existing progress object is used. If Progress is NULL, then no
1890 * progress object is created/used at all.
1891 *
1892 * When @a aWait is @c false, this method will create a thread to perform the
1893 * delete operation asynchronously and will return immediately. Otherwise, it
1894 * will perform the operation on the calling thread and will not return to the
1895 * caller until the operation is completed. Note that @a aProgress cannot be
1896 * NULL when @a aWait is @c false (this method will assert in this case).
1897 *
1898 * @param aProgress Where to find/store a Progress object to track operation
1899 * completion.
1900 * @param aWait @c true if this method should block instead of creating
1901 * an asynchronous thread.
1902 *
1903 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
1904 * writing.
1905 */
1906HRESULT HardDisk2::deleteStorage (ComObjPtr <Progress> *aProgress, bool aWait)
1907{
1908 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
1909
1910 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
1911 * ourselves atomically after detecting that deletion is possible to make
1912 * sure that we don't do that after another thread has done
1913 * VirtualBox::findHardDisk2() but before it starts using us (provided that
1914 * it holds a mVirtualBox lock too of course). */
1915
1916 AutoWriteLock vboxLock (mVirtualBox);
1917
1918 AutoWriteLock alock (this);
1919
1920 if (!(mm.formatObj->capabilities() &
1921 (HardDiskFormatCapabilities_CreateDynamic |
1922 HardDiskFormatCapabilities_CreateFixed)))
1923 return setError (VBOX_E_NOT_SUPPORTED,
1924 tr ("Hard disk format '%ls' does not support storage deletion"),
1925 mm.format.raw());
1926
1927 /* Note that we are fine with Inaccessible state too: a) for symmetry with
1928 * create calls and b) because it doesn't really harm to try, if it is
1929 * really inaccessibke, the delete operation will fail anyway. Accepting
1930 * Inaccessible state is especially important because all registered hard
1931 * disks are initially Inaccessible upon VBoxSVC startup until
1932 * COMGETTER(State) is called. */
1933
1934 switch (m.state)
1935 {
1936 case MediaState_Created:
1937 case MediaState_Inaccessible:
1938 break;
1939 default:
1940 return setStateError();
1941 }
1942
1943 if (m.backRefs.size() != 0)
1944 return setError (VBOX_E_OBJECT_IN_USE,
1945 tr ("Hard disk '%ls' is attached to %d virtual machines"),
1946 m.locationFull.raw(), m.backRefs.size());
1947
1948 HRESULT rc = canClose();
1949 CheckComRCReturnRC (rc);
1950
1951 /* go to Deleting state before leaving the lock */
1952 m.state = MediaState_Deleting;
1953
1954 /* we need to leave this object's write lock now because of
1955 * unregisterWithVirtualBox() that locks treeLock() for writing */
1956 alock.leave();
1957
1958 /* try to remove from the list of known hard disks before performing actual
1959 * deletion (we favor the consistency of the media registry in the first
1960 * place which would have been broken if unregisterWithVirtualBox() failed
1961 * after we successfully deleted the storage) */
1962
1963 rc = unregisterWithVirtualBox();
1964
1965 alock.enter();
1966
1967 /* restore the state because we may fail below; we will set it later again*/
1968 m.state = MediaState_Created;
1969
1970 CheckComRCReturnRC (rc);
1971
1972 ComObjPtr <Progress> progress;
1973
1974 if (aProgress != NULL)
1975 {
1976 /* use the existing progress object... */
1977 progress = *aProgress;
1978
1979 /* ...but create a new one if it is null */
1980 if (progress.isNull())
1981 {
1982 progress.createObject();
1983 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1984 BstrFmt (tr ("Deleting hard disk storage unit '%ls'"),
1985 name().raw()),
1986 FALSE /* aCancelable */);
1987 CheckComRCReturnRC (rc);
1988 }
1989 }
1990
1991 std::auto_ptr <Task> task (new Task (this, progress, Task::Delete));
1992 AssertComRCReturnRC (task->autoCaller.rc());
1993
1994 if (aWait)
1995 {
1996 /* go to Deleting state before starting the task */
1997 m.state = MediaState_Deleting;
1998
1999 rc = task->runNow();
2000 }
2001 else
2002 {
2003 rc = task->startThread();
2004 CheckComRCReturnRC (rc);
2005
2006 /* go to Deleting state before leaving the lock */
2007 m.state = MediaState_Deleting;
2008 }
2009
2010 /* task is now owned (or already deleted) by taskThread() so release it */
2011 task.release();
2012
2013 if (aProgress != NULL)
2014 {
2015 /* return progress to the caller */
2016 *aProgress = progress;
2017 }
2018
2019 return rc;
2020}
2021
2022/**
2023 * Creates a new differencing storage unit using the given target hard disk's
2024 * format and the location. Note that @c aTarget must be NotCreated.
2025 *
2026 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
2027 * this hard disk for reading assuming that the caller has already done so. This
2028 * is used when taking an online snaopshot (where all original hard disks are
2029 * locked for writing and must remain such). Note however that if @a aWait is
2030 * @c false and this method returns a success then the thread started by
2031 * this method will unlock the hard disk (unless it is in
2032 * MediaState_LockedWrite state) so make sure the hard disk is either in
2033 * MediaState_LockedWrite or call #LockRead() before calling this method! If @a
2034 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
2035 * make sure you do it yourself as needed.
2036 *
2037 * If @a aProgress is not NULL but the object it points to is @c null then a new
2038 * progress object will be created and assigned to @a *aProgress on success,
2039 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
2040 * progress object is created/used at all.
2041 *
2042 * When @a aWait is @c false, this method will create a thread to perform the
2043 * create operation asynchronously and will return immediately. Otherwise, it
2044 * will perform the operation on the calling thread and will not return to the
2045 * caller until the operation is completed. Note that @a aProgress cannot be
2046 * NULL when @a aWait is @c false (this method will assert in this case).
2047 *
2048 * @param aTarget Target hard disk.
2049 * @param aProgress Where to find/store a Progress object to track operation
2050 * completion.
2051 * @param aWait @c true if this method should block instead of creating
2052 * an asynchronous thread.
2053 *
2054 * @note Locks this object and @a aTarget for writing.
2055 */
2056HRESULT HardDisk2::createDiffStorage (ComObjPtr <HardDisk2> &aTarget,
2057 ComObjPtr <Progress> *aProgress,
2058 bool aWait)
2059{
2060 AssertReturn (!aTarget.isNull(), E_FAIL);
2061 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2062
2063 AutoCaller autoCaller (this);
2064 CheckComRCReturnRC (autoCaller.rc());
2065
2066 AutoCaller targetCaller (aTarget);
2067 CheckComRCReturnRC (targetCaller.rc());
2068
2069 AutoMultiWriteLock2 alock (this, aTarget);
2070
2071 AssertReturn (mm.type != HardDiskType_Writethrough, E_FAIL);
2072
2073 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
2074 AssertReturn (m.state == MediaState_LockedRead ||
2075 m.state == MediaState_LockedWrite, E_FAIL);
2076
2077 if (aTarget->m.state != MediaState_NotCreated)
2078 return aTarget->setStateError();
2079
2080 HRESULT rc = S_OK;
2081
2082 /* check that the hard disk is not attached to any VM in the current state*/
2083 for (BackRefList::const_iterator it = m.backRefs.begin();
2084 it != m.backRefs.end(); ++ it)
2085 {
2086 if (it->inCurState)
2087 {
2088 /* Note: when a VM snapshot is being taken, all normal hard disks
2089 * attached to the VM in the current state will be, as an exception,
2090 * also associated with the snapshot which is about to create (see
2091 * SnapshotMachine::init()) before deassociating them from the
2092 * current state (which takes place only on success in
2093 * Machine::fixupHardDisks2()), so that the size of snapshotIds
2094 * will be 1 in this case. The given condition is used to filter out
2095 * this legal situatinon and do not report an error. */
2096
2097 if (it->snapshotIds.size() == 0)
2098 {
2099 return setError (VBOX_E_INVALID_OBJECT_STATE,
2100 tr ("Hard disk '%ls' is attached to a virtual machine "
2101 "with UUID {%RTuuid}. No differencing hard disks "
2102 "based on it may be created until it is detached"),
2103 m.location.raw(), it->machineId.raw());
2104 }
2105
2106 Assert (it->snapshotIds.size() == 1);
2107 }
2108 }
2109
2110 ComObjPtr <Progress> progress;
2111
2112 if (aProgress != NULL)
2113 {
2114 /* use the existing progress object... */
2115 progress = *aProgress;
2116
2117 /* ...but create a new one if it is null */
2118 if (progress.isNull())
2119 {
2120 progress.createObject();
2121 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
2122 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
2123 aTarget->name().raw()),
2124 FALSE /* aCancelable */);
2125 CheckComRCReturnRC (rc);
2126 }
2127 }
2128
2129 /* setup task object and thread to carry out the operation
2130 * asynchronously */
2131
2132 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
2133 AssertComRCReturnRC (task->autoCaller.rc());
2134
2135 task->setData (aTarget);
2136
2137 /* register a task (it will deregister itself when done) */
2138 ++ mm.numCreateDiffTasks;
2139 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
2140
2141 if (aWait)
2142 {
2143 /* go to Creating state before starting the task */
2144 aTarget->m.state = MediaState_Creating;
2145
2146 rc = task->runNow();
2147 }
2148 else
2149 {
2150 rc = task->startThread();
2151 CheckComRCReturnRC (rc);
2152
2153 /* go to Creating state before leaving the lock */
2154 aTarget->m.state = MediaState_Creating;
2155 }
2156
2157 /* task is now owned (or already deleted) by taskThread() so release it */
2158 task.release();
2159
2160 if (aProgress != NULL)
2161 {
2162 /* return progress to the caller */
2163 *aProgress = progress;
2164 }
2165
2166 return rc;
2167}
2168
2169/**
2170 * Prepares this (source) hard disk, target hard disk and all intermediate hard
2171 * disks for the merge operation.
2172 *
2173 * This method is to be called prior to calling the #mergeTo() to perform
2174 * necessary consistency checks and place involved hard disks to appropriate
2175 * states. If #mergeTo() is not called or fails, the state modifications
2176 * performed by this method must be undone by #cancelMergeTo().
2177 *
2178 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
2179 * responsibility to detach the source and all intermediate hard disks before
2180 * calling #mergeTo() (which will fail otherwise).
2181 *
2182 * See #mergeTo() for more information about merging.
2183 *
2184 * @param aTarget Target hard disk.
2185 * @param aChain Where to store the created merge chain.
2186 * @param aIgnoreAttachments Don't check if the source or any intermediate
2187 * hard disk is attached to any VM.
2188 *
2189 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2190 * intermediate hard disks for writing.
2191 */
2192HRESULT HardDisk2::prepareMergeTo (HardDisk2 *aTarget,
2193 MergeChain * &aChain,
2194 bool aIgnoreAttachments /*= false*/)
2195{
2196 AssertReturn (aTarget != NULL, E_FAIL);
2197
2198 AutoCaller autoCaller (this);
2199 AssertComRCReturnRC (autoCaller.rc());
2200
2201 AutoCaller targetCaller (aTarget);
2202 AssertComRCReturnRC (targetCaller.rc());
2203
2204 aChain = NULL;
2205
2206 /* we walk the tree */
2207 AutoReadLock treeLock (this->treeLock());
2208
2209 HRESULT rc = S_OK;
2210
2211 /* detect the merge direction */
2212 bool forward;
2213 {
2214 HardDisk2 *parent = mParent;
2215 while (parent != NULL && parent != aTarget)
2216 parent = parent->mParent;
2217 if (parent == aTarget)
2218 forward = false;
2219 else
2220 {
2221 parent = aTarget->mParent;
2222 while (parent != NULL && parent != this)
2223 parent = parent->mParent;
2224 if (parent == this)
2225 forward = true;
2226 else
2227 {
2228 Bstr tgtLoc;
2229 {
2230 AutoReadLock alock (this);
2231 tgtLoc = aTarget->locationFull();
2232 }
2233
2234 AutoReadLock alock (this);
2235 return setError (E_FAIL,
2236 tr ("Hard disks '%ls' and '%ls' are unrelated"),
2237 m.locationFull.raw(), tgtLoc.raw());
2238 }
2239 }
2240 }
2241
2242 /* build the chain (will do necessary checks and state changes) */
2243 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2244 aIgnoreAttachments));
2245 {
2246 HardDisk2 *last = forward ? aTarget : this;
2247 HardDisk2 *first = forward ? this : aTarget;
2248
2249 for (;;)
2250 {
2251 if (last == aTarget)
2252 rc = chain->addTarget (last);
2253 else if (last == this)
2254 rc = chain->addSource (last);
2255 else
2256 rc = chain->addIntermediate (last);
2257 CheckComRCReturnRC (rc);
2258
2259 if (last == first)
2260 break;
2261
2262 last = last->mParent;
2263 }
2264 }
2265
2266 aChain = chain.release();
2267
2268 return S_OK;
2269}
2270
2271/**
2272 * Merges this hard disk to the specified hard disk which must be either its
2273 * direct ancestor or descendant.
2274 *
2275 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2276 * get two varians of the merge operation:
2277 *
2278 * forward merge
2279 * ------------------------->
2280 * [Extra] <- SOURCE <- Intermediate <- TARGET
2281 * Any Del Del LockWr
2282 *
2283 *
2284 * backward merge
2285 * <-------------------------
2286 * TARGET <- Intermediate <- SOURCE <- [Extra]
2287 * LockWr Del Del LockWr
2288 *
2289 * Each scheme shows the involved hard disks on the hard disk chain where
2290 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2291 * the hard disk must have at a time of the mergeTo() call.
2292 *
2293 * The hard disks in the square braces may be absent (e.g. when the forward
2294 * operation takes place and SOURCE is the base hard disk, or when the backward
2295 * merge operation takes place and TARGET is the last child in the chain) but if
2296 * they present they are involved too as shown.
2297 *
2298 * Nor the source hard disk neither intermediate hard disks may be attached to
2299 * any VM directly or in the snapshot, otherwise this method will assert.
2300 *
2301 * The #prepareMergeTo() method must be called prior to this method to place all
2302 * involved to necessary states and perform other consistency checks.
2303 *
2304 * If @a aWait is @c true then this method will perform the operation on the
2305 * calling thread and will not return to the caller until the operation is
2306 * completed. When this method succeeds, all intermediate hard disk objects in
2307 * the chain will be uninitialized, the state of the target hard disk (and all
2308 * involved extra hard disks) will be restored and @a aChain will be deleted.
2309 * Note that this (source) hard disk is not uninitialized because of possible
2310 * AutoCaller instances held by the caller of this method on the current thread.
2311 * It's therefore the responsibility of the caller to call HardDisk2::uninit()
2312 * after releasing all callers in this case!
2313 *
2314 * If @a aWait is @c false then this method will crea,te a thread to perform the
2315 * create operation asynchronously and will return immediately. If the operation
2316 * succeeds, the thread will uninitialize the source hard disk object and all
2317 * intermediate hard disk objects in the chain, reset the state of the target
2318 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2319 * operation fails, the thread will only reset the states of all involved hard
2320 * disks and delete @a aChain.
2321 *
2322 * When this method fails (regardless of the @a aWait mode), it is a caller's
2323 * responsiblity to undo state changes and delete @a aChain using
2324 * #cancelMergeTo().
2325 *
2326 * If @a aProgress is not NULL but the object it points to is @c null then a new
2327 * progress object will be created and assigned to @a *aProgress on success,
2328 * otherwise the existing progress object is used. If Progress is NULL, then no
2329 * progress object is created/used at all. Note that @a aProgress cannot be
2330 * NULL when @a aWait is @c false (this method will assert in this case).
2331 *
2332 * @param aChain Merge chain created by #prepareMergeTo().
2333 * @param aProgress Where to find/store a Progress object to track operation
2334 * completion.
2335 * @param aWait @c true if this method should block instead of creating
2336 * an asynchronous thread.
2337 *
2338 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2339 * for writing.
2340 */
2341HRESULT HardDisk2::mergeTo (MergeChain *aChain,
2342 ComObjPtr <Progress> *aProgress,
2343 bool aWait)
2344{
2345 AssertReturn (aChain != NULL, E_FAIL);
2346 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2347
2348 AutoCaller autoCaller (this);
2349 CheckComRCReturnRC (autoCaller.rc());
2350
2351 HRESULT rc = S_OK;
2352
2353 ComObjPtr <Progress> progress;
2354
2355 if (aProgress != NULL)
2356 {
2357 /* use the existing progress object... */
2358 progress = *aProgress;
2359
2360 /* ...but create a new one if it is null */
2361 if (progress.isNull())
2362 {
2363 AutoReadLock alock (this);
2364
2365 progress.createObject();
2366 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
2367 BstrFmt (tr ("Merging hard disk '%ls' to '%ls'"),
2368 name().raw(), aChain->target()->name().raw()),
2369 FALSE /* aCancelable */);
2370 CheckComRCReturnRC (rc);
2371 }
2372 }
2373
2374 /* setup task object and thread to carry out the operation
2375 * asynchronously */
2376
2377 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2378 AssertComRCReturnRC (task->autoCaller.rc());
2379
2380 task->setData (aChain);
2381
2382 /* Note: task owns aChain (will delete it when not needed) in all cases
2383 * except when @a aWait is @c true and runNow() fails -- in this case
2384 * aChain will be left away because cancelMergeTo() will be applied by the
2385 * caller on it as it is required in the documentation above */
2386
2387 if (aWait)
2388 {
2389 rc = task->runNow();
2390 }
2391 else
2392 {
2393 rc = task->startThread();
2394 CheckComRCReturnRC (rc);
2395 }
2396
2397 /* task is now owned (or already deleted) by taskThread() so release it */
2398 task.release();
2399
2400 if (aProgress != NULL)
2401 {
2402 /* return progress to the caller */
2403 *aProgress = progress;
2404 }
2405
2406 return rc;
2407}
2408
2409/**
2410 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2411 * or fails. Frees memory occupied by @a aChain.
2412 *
2413 * @param aChain Merge chain created by #prepareMergeTo().
2414 *
2415 * @note Locks the hard disks from the chain for writing.
2416 */
2417void HardDisk2::cancelMergeTo (MergeChain *aChain)
2418{
2419 AutoCaller autoCaller (this);
2420 AssertComRCReturnVoid (autoCaller.rc());
2421
2422 AssertReturnVoid (aChain != NULL);
2423
2424 /* the destructor will do the thing */
2425 delete aChain;
2426}
2427
2428// private methods
2429////////////////////////////////////////////////////////////////////////////////
2430
2431/**
2432 * Sets the value of m.location and calculates the value of m.locationFull.
2433 *
2434 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2435 * locations and to prepend the default hard disk folder if the given location
2436 * string does not contain any path information at all.
2437 *
2438 * Also, if the specified location is a file path that ends with '/' then the
2439 * file name part will be generated by this method automatically in the format
2440 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2441 * and assign to this medium, and <ext> is the default extension for this
2442 * medium's storage format. Note that this procedure requires the media state to
2443 * be NotCreated and will return a faiulre otherwise.
2444 *
2445 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2446 * then it can be relative to the VirtualBox home directory.
2447 *
2448 * @note Must be called from under this object's write lock.
2449 */
2450HRESULT HardDisk2::setLocation (CBSTR aLocation)
2451{
2452 /// @todo so far, we assert but later it makes sense to support null
2453 /// locations for hard disks that are not yet created fail to create a
2454 /// storage unit instead
2455 CheckComArgStrNotEmptyOrNull (aLocation);
2456
2457 AutoCaller autoCaller (this);
2458 AssertComRCReturnRC (autoCaller.rc());
2459
2460 /* formatObj may be null only when initializing from an existing path and
2461 * no format is known yet */
2462 AssertReturn ((!mm.format.isNull() && !mm.formatObj.isNull()) ||
2463 (autoCaller.state() == InInit &&
2464 m.state != MediaState_NotCreated && m.id.isEmpty() &&
2465 mm.format.isNull() && mm.formatObj.isNull()),
2466 E_FAIL);
2467
2468 /* are we dealing with a new hard disk constructed using the existing
2469 * location? */
2470 bool isImport = mm.format.isNull();
2471
2472 if (isImport ||
2473 (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File))
2474 {
2475 Guid id;
2476
2477 Utf8Str location (aLocation);
2478
2479 if (m.state == MediaState_NotCreated)
2480 {
2481 /* must be a file (formatObj must be already known) */
2482 Assert (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File);
2483
2484 if (RTPathFilename (location) == NULL)
2485 {
2486 /* no file name is given (either an empty string or ends with a
2487 * slash), generate a new UUID + file name if the state allows
2488 * this */
2489
2490 ComAssertMsgRet (!mm.formatObj->fileExtensions().empty(),
2491 ("Must be at least one extension if it is "
2492 "HardDiskFormatCapabilities_File\n"),
2493 E_FAIL);
2494
2495 Bstr ext = mm.formatObj->fileExtensions().front();
2496 ComAssertMsgRet (!ext.isEmpty(),
2497 ("Default extension must not be empty\n"),
2498 E_FAIL);
2499
2500 id.create();
2501
2502 location = Utf8StrFmt ("%s{%RTuuid}.%ls",
2503 location.raw(), id.raw(), ext.raw());
2504 }
2505 }
2506
2507 /* append the default folder if no path is given */
2508 if (!RTPathHavePath (location))
2509 {
2510 AutoReadLock propsLock (mVirtualBox->systemProperties());
2511 location = Utf8StrFmt ("%ls%c%s",
2512 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2513 RTPATH_DELIMITER,
2514 location.raw());
2515 }
2516
2517 /* get the full file name */
2518 Utf8Str locationFull;
2519 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
2520 if (RT_FAILURE (vrc))
2521 return setError (VBOX_E_FILE_ERROR,
2522 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2523 location.raw(), vrc);
2524
2525 /* detect the backend from the storage unit if importing */
2526 if (isImport)
2527 {
2528 char *backendName = NULL;
2529
2530 /* is it a file? */
2531 {
2532 RTFILE file;
2533 vrc = RTFileOpen (&file, locationFull, RTFILE_O_READ);
2534 if (RT_SUCCESS (vrc))
2535 RTFileClose (file);
2536 }
2537 if (RT_SUCCESS (vrc))
2538 {
2539 vrc = VDGetFormat (locationFull, &backendName);
2540 }
2541 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2542 {
2543 /* assume it's not a file, restore the original location */
2544 location = locationFull = aLocation;
2545 vrc = VDGetFormat (locationFull, &backendName);
2546 }
2547
2548 if (RT_FAILURE (vrc))
2549 return setError (VBOX_E_IPRT_ERROR,
2550 tr ("Could not get the storage format of the hard disk "
2551 "'%s' (%Rrc)"), locationFull.raw(), vrc);
2552
2553 ComAssertRet (backendName != NULL && *backendName != '\0', E_FAIL);
2554
2555 HRESULT rc = setFormat (Bstr (backendName));
2556 RTStrFree (backendName);
2557
2558 /* setFormat() must not fail since we've just used the backend so
2559 * the format object must be there */
2560 AssertComRCReturnRC (rc);
2561 }
2562
2563 /* is it still a file? */
2564 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
2565 {
2566 m.location = location;
2567 m.locationFull = locationFull;
2568
2569 if (m.state == MediaState_NotCreated)
2570 {
2571 /* assign a new UUID (this UUID will be used when calling
2572 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
2573 * also do that if we didn't generate it to make sure it is
2574 * either generated by us or reset to null */
2575 unconst (m.id) = id;
2576 }
2577 }
2578 else
2579 {
2580 m.location = locationFull;
2581 m.locationFull = locationFull;
2582 }
2583 }
2584 else
2585 {
2586 m.location = aLocation;
2587 m.locationFull = aLocation;
2588 }
2589
2590 return S_OK;
2591}
2592
2593/**
2594 * Checks that the format ID is valid and sets it on success.
2595 *
2596 * Note that this method will caller-reference the format object on success!
2597 * This reference must be released somewhere to let the HardDiskFormat object be
2598 * uninitialized.
2599 *
2600 * @note Must be called from under this object's write lock.
2601 */
2602HRESULT HardDisk2::setFormat (CBSTR aFormat)
2603{
2604 /* get the format object first */
2605 {
2606 AutoReadLock propsLock (mVirtualBox->systemProperties());
2607
2608 unconst (mm.formatObj)
2609 = mVirtualBox->systemProperties()->hardDiskFormat (aFormat);
2610 if (mm.formatObj.isNull())
2611 return setError (E_INVALIDARG,
2612 tr ("Invalid hard disk storage format '%ls'"), aFormat);
2613
2614 /* reference the format permanently to prevent its unexpected
2615 * uninitialization */
2616 HRESULT rc = mm.formatObj->addCaller();
2617 AssertComRCReturnRC (rc);
2618
2619 /* get properties (preinsert them as keys in the map). Note that the
2620 * map doesn't grow over the object life time since the set of
2621 * properties is meant to be constant. */
2622
2623 Assert (mm.properties.empty());
2624
2625 for (HardDiskFormat::PropertyList::const_iterator it =
2626 mm.formatObj->properties().begin();
2627 it != mm.formatObj->properties().end();
2628 ++ it)
2629 {
2630 mm.properties.insert (std::make_pair (it->name, Bstr::Null));
2631 }
2632 }
2633
2634 unconst (mm.format) = aFormat;
2635
2636 return S_OK;
2637}
2638
2639/**
2640 * Queries information from the image file.
2641 *
2642 * As a result of this call, the accessibility state and data members such as
2643 * size and description will be updated with the current information.
2644 *
2645 * Reimplements MediumBase::queryInfo() to query hard disk information using the
2646 * VD backend interface.
2647 *
2648 * @note This method may block during a system I/O call that checks storage
2649 * accessibility.
2650 *
2651 * @note Locks treeLock() for reading and writing (for new diff media checked
2652 * for the first time). Locks mParent for reading. Locks this object for
2653 * writing.
2654 */
2655HRESULT HardDisk2::queryInfo()
2656{
2657 AutoWriteLock alock (this);
2658
2659 AssertReturn (m.state == MediaState_Created ||
2660 m.state == MediaState_Inaccessible ||
2661 m.state == MediaState_LockedRead ||
2662 m.state == MediaState_LockedWrite,
2663 E_FAIL);
2664
2665 HRESULT rc = S_OK;
2666
2667 int vrc = VINF_SUCCESS;
2668
2669 /* check if a blocking queryInfo() call is in progress on some other thread,
2670 * and wait for it to finish if so instead of querying data ourselves */
2671 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
2672 {
2673 Assert (m.state == MediaState_LockedRead);
2674
2675 ++ m.queryInfoCallers;
2676 alock.leave();
2677
2678 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
2679
2680 alock.enter();
2681 -- m.queryInfoCallers;
2682
2683 if (m.queryInfoCallers == 0)
2684 {
2685 /* last waiting caller deletes the semaphore */
2686 RTSemEventMultiDestroy (m.queryInfoSem);
2687 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2688 }
2689
2690 AssertRC (vrc);
2691
2692 return S_OK;
2693 }
2694
2695 /* lazily create a semaphore for possible callers */
2696 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
2697 ComAssertRCRet (vrc, E_FAIL);
2698
2699 bool tempStateSet = false;
2700 if (m.state != MediaState_LockedRead &&
2701 m.state != MediaState_LockedWrite)
2702 {
2703 /* Cause other methods to prevent any modifications before leaving the
2704 * lock. Note that clients will never see this temporary state change
2705 * since any COMGETTER(State) is (or will be) blocked until we finish
2706 * and restore the actual state. */
2707 m.state = MediaState_LockedRead;
2708 tempStateSet = true;
2709 }
2710
2711 /* leave the lock before a blocking operation */
2712 alock.leave();
2713
2714 bool success = false;
2715 Utf8Str lastAccessError;
2716
2717 try
2718 {
2719 Utf8Str location (m.locationFull);
2720
2721 /* are we dealing with a new hard disk constructed using the existing
2722 * location? */
2723 bool isImport = m.id.isEmpty();
2724
2725 PVBOXHDD hdd;
2726 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
2727 ComAssertRCThrow (vrc, E_FAIL);
2728
2729 try
2730 {
2731 unsigned flags = VD_OPEN_FLAGS_INFO;
2732
2733 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
2734 * hard disks because that would prevent necessary modifications
2735 * when opening hard disks of some third-party formats for the first
2736 * time in VirtualBox (such as VMDK for which VDOpen() needs to
2737 * generate an UUID if it is missing) */
2738 if (!isImport)
2739 flags |= VD_OPEN_FLAGS_READONLY;
2740
2741 vrc = VDOpen (hdd, Utf8Str (mm.format), location, flags,
2742 mm.vdDiskIfaces);
2743 if (RT_FAILURE (vrc))
2744 {
2745 lastAccessError = Utf8StrFmt (
2746 tr ("Could not open the hard disk '%ls'%s"),
2747 m.locationFull.raw(), vdError (vrc).raw());
2748 throw S_OK;
2749 }
2750
2751 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_Uuid)
2752 {
2753 /* check the UUID */
2754 RTUUID uuid;
2755 vrc = VDGetUuid (hdd, 0, &uuid);
2756 ComAssertRCThrow (vrc, E_FAIL);
2757
2758 if (isImport)
2759 {
2760 unconst (m.id) = uuid;
2761 }
2762 else
2763 {
2764 Assert (!m.id.isEmpty());
2765
2766 if (m.id != uuid)
2767 {
2768 lastAccessError = Utf8StrFmt (
2769 tr ("UUID {%RTuuid} of the hard disk '%ls' does "
2770 "not match the value {%RTuuid} stored in the "
2771 "media registry ('%ls')"),
2772 &uuid, m.locationFull.raw(), m.id.raw(),
2773 mVirtualBox->settingsFileName().raw());
2774 throw S_OK;
2775 }
2776 }
2777 }
2778 else
2779 {
2780 /* the backend does not support storing UUIDs within the
2781 * underlying storage so use what we store in XML */
2782
2783 /* generate an UUID for an imported UUID-less hard disk */
2784 if (isImport)
2785 unconst (m.id).create();
2786 }
2787
2788 /* check the type */
2789 VDIMAGETYPE type;
2790 vrc = VDGetImageType (hdd, 0, &type);
2791 ComAssertRCThrow (vrc, E_FAIL);
2792
2793 if (type == VD_IMAGE_TYPE_DIFF)
2794 {
2795 RTUUID parentId;
2796 vrc = VDGetParentUuid (hdd, 0, &parentId);
2797 ComAssertRCThrow (vrc, E_FAIL);
2798
2799 if (isImport)
2800 {
2801 /* the parent must be known to us. Note that we freely
2802 * call locking methods of mVirtualBox and parent from the
2803 * write lock (breaking the {parent,child} lock order)
2804 * because there may be no concurrent access to the just
2805 * opened hard disk on ther threads yet (and init() will
2806 * fail if this method reporst MediaState_Inaccessible) */
2807
2808 Guid id = parentId;
2809 ComObjPtr <HardDisk2> parent;
2810 rc = mVirtualBox->findHardDisk2 (&id, NULL,
2811 false /* aSetError */,
2812 &parent);
2813 if (FAILED (rc))
2814 {
2815 lastAccessError = Utf8StrFmt (
2816 tr ("Parent hard disk with UUID {%RTuuid} of the "
2817 "hard disk '%ls' is not found in the media "
2818 "registry ('%ls')"),
2819 &parentId, m.locationFull.raw(),
2820 mVirtualBox->settingsFileName().raw());
2821 throw S_OK;
2822 }
2823
2824 /* deassociate from VirtualBox, associate with parent */
2825
2826 mVirtualBox->removeDependentChild (this);
2827
2828 /* we set mParent & children() */
2829 AutoWriteLock treeLock (this->treeLock());
2830
2831 Assert (mParent.isNull());
2832 mParent = parent;
2833 mParent->addDependentChild (this);
2834 }
2835 else
2836 {
2837 /* we access mParent */
2838 AutoReadLock treeLock (this->treeLock());
2839
2840 /* check that parent UUIDs match. Note that there's no need
2841 * for the parent's AutoCaller (our lifetime is bound to
2842 * it) */
2843
2844 if (mParent.isNull())
2845 {
2846 lastAccessError = Utf8StrFmt (
2847 tr ("Hard disk '%ls' is differencing but it is not "
2848 "associated with any parent hard disk in the "
2849 "media registry ('%ls')"),
2850 m.locationFull.raw(),
2851 mVirtualBox->settingsFileName().raw());
2852 throw S_OK;
2853 }
2854
2855 AutoReadLock parentLock (mParent);
2856 if (mParent->state() != MediaState_Inaccessible &&
2857 mParent->id() != parentId)
2858 {
2859 lastAccessError = Utf8StrFmt (
2860 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' "
2861 "does not match UUID {%RTuuid} of its parent "
2862 "hard disk stored in the media registry ('%ls')"),
2863 &parentId, m.locationFull.raw(),
2864 mParent->id().raw(),
2865 mVirtualBox->settingsFileName().raw());
2866 throw S_OK;
2867 }
2868
2869 /// @todo NEWMEDIA what to do if the parent is not
2870 /// accessible while the diff is? Probably, nothing. The
2871 /// real code will detect the mismatch anyway.
2872 }
2873 }
2874
2875 m.size = VDGetFileSize (hdd, 0);
2876 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
2877
2878 success = true;
2879 }
2880 catch (HRESULT aRC)
2881 {
2882 rc = aRC;
2883 }
2884
2885 VDDestroy (hdd);
2886
2887 }
2888 catch (HRESULT aRC)
2889 {
2890 rc = aRC;
2891 }
2892
2893 alock.enter();
2894
2895 if (success)
2896 m.lastAccessError.setNull();
2897 else
2898 {
2899 m.lastAccessError = lastAccessError;
2900 LogWarningFunc (("'%ls' is not accessible (error='%ls', "
2901 "rc=%Rhrc, vrc=%Rrc)\n",
2902 m.locationFull.raw(), m.lastAccessError.raw(),
2903 rc, vrc));
2904 }
2905
2906 /* inform other callers if there are any */
2907 if (m.queryInfoCallers > 0)
2908 {
2909 RTSemEventMultiSignal (m.queryInfoSem);
2910 }
2911 else
2912 {
2913 /* delete the semaphore ourselves */
2914 RTSemEventMultiDestroy (m.queryInfoSem);
2915 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2916 }
2917
2918 if (tempStateSet)
2919 {
2920 /* Set the proper state according to the result of the check */
2921 if (success)
2922 m.state = MediaState_Created;
2923 else
2924 m.state = MediaState_Inaccessible;
2925 }
2926 else
2927 {
2928 /* we're locked, use a special field to store the result */
2929 m.accessibleInLock = success;
2930 }
2931
2932 return rc;
2933}
2934
2935/**
2936 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
2937 * write lock.
2938 *
2939 * @note Locks treeLock() for reading.
2940 */
2941HRESULT HardDisk2::canClose()
2942{
2943 /* we access children */
2944 AutoReadLock treeLock (this->treeLock());
2945
2946 if (children().size() != 0)
2947 return setError (E_FAIL,
2948 tr ("Hard disk '%ls' has %d child hard disks"),
2949 children().size());
2950
2951 return S_OK;
2952}
2953
2954/**
2955 * @note Called from within this object's AutoWriteLock.
2956 */
2957HRESULT HardDisk2::canAttach (const Guid &aMachineId,
2958 const Guid &aSnapshotId)
2959{
2960 if (mm.numCreateDiffTasks > 0)
2961 return setError (E_FAIL,
2962 tr ("One or more differencing child hard disks are "
2963 "being created for the hard disk '%ls' (%u)"),
2964 m.locationFull.raw(), mm.numCreateDiffTasks);
2965
2966 return S_OK;
2967}
2968
2969/**
2970 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
2971 * from under mVirtualBox write lock.
2972 *
2973 * @note Locks treeLock() for writing.
2974 */
2975HRESULT HardDisk2::unregisterWithVirtualBox()
2976{
2977 /* Note that we need to de-associate ourselves from the parent to let
2978 * unregisterHardDisk2() properly save the registry */
2979
2980 /* we modify mParent and access children */
2981 AutoWriteLock treeLock (this->treeLock());
2982
2983 const ComObjPtr <HardDisk2, ComWeakRef> parent = mParent;
2984
2985 AssertReturn (children().size() == 0, E_FAIL);
2986
2987 if (!mParent.isNull())
2988 {
2989 /* deassociate from the parent, associate with VirtualBox */
2990 mVirtualBox->addDependentChild (this);
2991 mParent->removeDependentChild (this);
2992 mParent.setNull();
2993 }
2994
2995 HRESULT rc = mVirtualBox->unregisterHardDisk2 (this);
2996
2997 if (FAILED (rc))
2998 {
2999 if (!parent.isNull())
3000 {
3001 /* re-associate with the parent as we are still relatives in the
3002 * registry */
3003 mParent = parent;
3004 mParent->addDependentChild (this);
3005 mVirtualBox->removeDependentChild (this);
3006 }
3007 }
3008
3009 return rc;
3010}
3011
3012/**
3013 * Returns the last error message collected by the vdErrorCall callback and
3014 * resets it.
3015 *
3016 * The error message is returned prepended with a dot and a space, like this:
3017 * <code>
3018 * ". <error_text> (%Rrc)"
3019 * </code>
3020 * to make it easily appendable to a more general error message. The @c %Rrc
3021 * format string is given @a aVRC as an argument.
3022 *
3023 * If there is no last error message collected by vdErrorCall or if it is a
3024 * null or empty string, then this function returns the following text:
3025 * <code>
3026 * " (%Rrc)"
3027 * </code>
3028 *
3029 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3030 * the callback isn't called by more than one thread at a time.
3031 *
3032 * @param aVRC VBox error code to use when no error message is provided.
3033 */
3034Utf8Str HardDisk2::vdError (int aVRC)
3035{
3036 Utf8Str error;
3037
3038 if (mm.vdError.isEmpty())
3039 error = Utf8StrFmt (" (%Rrc)", aVRC);
3040 else
3041 error = Utf8StrFmt (".\n%s", mm.vdError.raw());
3042
3043 mm.vdError.setNull();
3044
3045 return error;
3046}
3047
3048/**
3049 * Error message callback.
3050 *
3051 * Puts the reported error message to the mm.vdError field.
3052 *
3053 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3054 * the callback isn't called by more than one thread at a time.
3055 *
3056 * @param pvUser The opaque data passed on container creation.
3057 * @param rc The VBox error code.
3058 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
3059 * @param pszFormat Error message format string.
3060 * @param va Error message arguments.
3061 */
3062/*static*/
3063DECLCALLBACK(void) HardDisk2::vdErrorCall (void *pvUser, int rc, RT_SRC_POS_DECL,
3064 const char *pszFormat, va_list va)
3065{
3066 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
3067 AssertReturnVoid (that != NULL);
3068
3069 if (that->mm.vdError.isEmpty())
3070 that->mm.vdError =
3071 Utf8StrFmt ("%s (%Rrc)", Utf8StrFmtVA (pszFormat, va).raw(), rc);
3072 else
3073 that->mm.vdError =
3074 Utf8StrFmt ("%s.\n%s (%Rrc)", that->mm.vdError.raw(),
3075 Utf8StrFmtVA (pszFormat, va).raw(), rc);
3076}
3077
3078/**
3079 * PFNVMPROGRESS callback handler for Task operations.
3080 *
3081 * @param uPercent Completetion precentage (0-100).
3082 * @param pvUser Pointer to the Progress instance.
3083 */
3084/*static*/
3085DECLCALLBACK(int) HardDisk2::vdProgressCall (PVM /* pVM */, unsigned uPercent,
3086 void *pvUser)
3087{
3088 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
3089 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3090
3091 if (that->mm.vdProgress != NULL)
3092 {
3093 /* update the progress object, capping it at 99% as the final percent
3094 * is used for additional operations like setting the UUIDs and similar. */
3095 that->mm.vdProgress->notifyProgress (RT_MIN (uPercent, 99));
3096 }
3097
3098 return VINF_SUCCESS;
3099}
3100
3101/* static */
3102DECLCALLBACK(bool) HardDisk2::vdConfigAreKeysValid (void *pvUser,
3103 const char *pszzValid)
3104{
3105 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
3106 AssertReturn (that != NULL, false);
3107
3108 /* we always return true since the only keys we have are those found in
3109 * VDBACKENDINFO */
3110 return true;
3111}
3112
3113/* static */
3114DECLCALLBACK(int) HardDisk2::vdConfigQuerySize (void *pvUser, const char *pszName,
3115 size_t *pcbValue)
3116{
3117 AssertReturn (VALID_PTR (pcbValue), VERR_INVALID_POINTER);
3118
3119 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
3120 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3121
3122 Data::PropertyMap::const_iterator it =
3123 that->mm.properties.find (Bstr (pszName));
3124 if (it == that->mm.properties.end())
3125 return VERR_CFGM_VALUE_NOT_FOUND;
3126
3127 /* we interpret null values as "no value" in HardDisk2 */
3128 if (it->second.isNull())
3129 return VERR_CFGM_VALUE_NOT_FOUND;
3130
3131 *pcbValue = it->second.length() + 1 /* include terminator */;
3132
3133 return VINF_SUCCESS;
3134}
3135
3136/* static */
3137DECLCALLBACK(int) HardDisk2::vdConfigQuery (void *pvUser, const char *pszName,
3138 char *pszValue, size_t cchValue)
3139{
3140 AssertReturn (VALID_PTR (pszValue), VERR_INVALID_POINTER);
3141
3142 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
3143 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3144
3145 Data::PropertyMap::const_iterator it =
3146 that->mm.properties.find (Bstr (pszName));
3147 if (it == that->mm.properties.end())
3148 return VERR_CFGM_VALUE_NOT_FOUND;
3149
3150 Utf8Str value = it->second;
3151 if (value.length() >= cchValue)
3152 return VERR_CFGM_NOT_ENOUGH_SPACE;
3153
3154 /* we interpret null values as "no value" in HardDisk2 */
3155 if (it->second.isNull())
3156 return VERR_CFGM_VALUE_NOT_FOUND;
3157
3158 memcpy (pszValue, value, value.length() + 1);
3159
3160 return VINF_SUCCESS;
3161}
3162
3163/**
3164 * Thread function for time-consuming tasks.
3165 *
3166 * The Task structure passed to @a pvUser must be allocated using new and will
3167 * be freed by this method before it returns.
3168 *
3169 * @param pvUser Pointer to the Task instance.
3170 */
3171/* static */
3172DECLCALLBACK(int) HardDisk2::taskThread (RTTHREAD thread, void *pvUser)
3173{
3174 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
3175 AssertReturn (task.get(), VERR_GENERAL_FAILURE);
3176
3177 bool isAsync = thread != NIL_RTTHREAD;
3178
3179 HardDisk2 *that = task->that;
3180
3181 /// @todo ugly hack, fix ComAssert... later
3182 #define setError that->setError
3183
3184 /* Note: no need in AutoCaller because Task does that */
3185
3186 LogFlowFuncEnter();
3187 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
3188
3189 HRESULT rc = S_OK;
3190
3191 switch (task->operation)
3192 {
3193 ////////////////////////////////////////////////////////////////////////
3194
3195 case Task::CreateDynamic:
3196 case Task::CreateFixed:
3197 {
3198 /* The lock is also used as a signal from the task initiator (which
3199 * releases it only after RTThreadCreate()) that we can start the job */
3200 AutoWriteLock thatLock (that);
3201
3202 /* these parameters we need after creation */
3203 uint64_t size = 0, logicalSize = 0;
3204
3205 /* the object may request a specific UUID (through a special
3206 * form of the setLocation() argumet) */
3207 Guid id = that->m.id;
3208
3209 try
3210 {
3211 PVBOXHDD hdd;
3212 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3213 ComAssertRCThrow (vrc, E_FAIL);
3214
3215 Utf8Str format (that->mm.format);
3216 Utf8Str location (that->m.locationFull);
3217 uint64_t capabilities = that->mm.formatObj->capabilities();
3218
3219 /* unlock before the potentially lengthy operation */
3220 Assert (that->m.state == MediaState_Creating);
3221 thatLock.leave();
3222
3223 try
3224 {
3225 /* ensure the directory exists */
3226 rc = VirtualBox::ensureFilePathExists (location);
3227 CheckComRCThrowRC (rc);
3228
3229 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
3230
3231 /* needed for vdProgressCallback */
3232 that->mm.vdProgress = task->progress;
3233
3234 vrc = VDCreateBase (hdd, format, location,
3235 task->operation == Task::CreateDynamic ?
3236 VD_IMAGE_TYPE_NORMAL :
3237 VD_IMAGE_TYPE_FIXED,
3238 task->d.size * _1M,
3239 VD_IMAGE_FLAGS_NONE,
3240 NULL, &geo, &geo,
3241 id.isEmpty() ? NULL : id.raw(),
3242 VD_OPEN_FLAGS_NORMAL,
3243 NULL, that->mm.vdDiskIfaces);
3244
3245 if (RT_FAILURE (vrc))
3246 {
3247 throw setError (E_FAIL,
3248 tr ("Could not create the hard disk storage "
3249 "unit '%s'%s"),
3250 location.raw(), that->vdError (vrc).raw());
3251 }
3252
3253 if (capabilities & HardDiskFormatCapabilities_Uuid)
3254 {
3255 RTUUID uuid;
3256 vrc = VDGetUuid (hdd, 0, &uuid);
3257 ComAssertRCThrow (vrc, E_FAIL);
3258
3259 if (!id.isEmpty())
3260 Assert (id == uuid);
3261 else
3262 id = uuid;
3263 }
3264 else
3265 {
3266 /* we have to generate an UUID ourselves */
3267 id.create();
3268 }
3269
3270 size = VDGetFileSize (hdd, 0);
3271 logicalSize = VDGetSize (hdd, 0) / _1M;
3272 }
3273 catch (HRESULT aRC) { rc = aRC; }
3274
3275 VDDestroy (hdd);
3276 }
3277 catch (HRESULT aRC) { rc = aRC; }
3278
3279 if (SUCCEEDED (rc))
3280 {
3281 /* mVirtualBox->registerHardDisk2() needs a write lock */
3282 AutoWriteLock vboxLock (that->mVirtualBox);
3283 thatLock.enter();
3284
3285 unconst (that->m.id) = id;
3286
3287 that->m.size = size;
3288 that->mm.logicalSize = logicalSize;
3289
3290 /* register with mVirtualBox as the last step and move to
3291 * Created state only on success (leaving an orphan file is
3292 * better than breaking media registry consistency) */
3293 rc = that->mVirtualBox->registerHardDisk2 (that);
3294
3295 if (SUCCEEDED (rc))
3296 that->m.state = MediaState_Created;
3297 }
3298
3299 if (FAILED (rc))
3300 {
3301 thatLock.maybeEnter();
3302
3303 /* back to NotCreated on failiure */
3304 that->m.state = MediaState_NotCreated;
3305 }
3306
3307 break;
3308 }
3309
3310 ////////////////////////////////////////////////////////////////////////
3311
3312 case Task::CreateDiff:
3313 {
3314 ComObjPtr <HardDisk2> &target = task->d.target;
3315
3316 /* Lock both in {parent,child} order. The lock is also used as a
3317 * signal from the task initiator (which releases it only after
3318 * RTThreadCreate()) that we can start the job*/
3319 AutoMultiWriteLock2 thatLock (that, target);
3320
3321 uint64_t size = 0, logicalSize = 0;
3322
3323 try
3324 {
3325 PVBOXHDD hdd;
3326 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3327 ComAssertRCThrow (vrc, E_FAIL);
3328
3329 Utf8Str format (that->mm.format);
3330 Utf8Str location (that->m.locationFull);
3331
3332 Utf8Str targetFormat (target->mm.format);
3333 Utf8Str targetLocation (target->m.locationFull);
3334 Guid targetId = target->m.id;
3335
3336 /* UUID must have been set by setLocation() */
3337 Assert (!targetId.isEmpty());
3338
3339 Assert (target->m.state == MediaState_Creating);
3340
3341 /* Note: MediaState_LockedWrite is ok when taking an online
3342 * snapshot */
3343 Assert (that->m.state == MediaState_LockedRead ||
3344 that->m.state == MediaState_LockedWrite);
3345
3346 /* unlock before the potentially lengthy operation */
3347 thatLock.leave();
3348
3349 try
3350 {
3351 vrc = VDOpen (hdd, format, location,
3352 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3353 NULL);
3354 if (RT_FAILURE (vrc))
3355 {
3356 throw setError (E_FAIL,
3357 tr ("Could not open the hard disk storage "
3358 "unit '%s'%s"),
3359 location.raw(), that->vdError (vrc).raw());
3360 }
3361
3362 /* ensure the target directory exists */
3363 rc = VirtualBox::ensureFilePathExists (targetLocation);
3364 CheckComRCThrowRC (rc);
3365
3366 /* needed for vdProgressCallback */
3367 that->mm.vdProgress = task->progress;
3368
3369 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
3370 VD_IMAGE_FLAGS_NONE,
3371 NULL, targetId.raw(),
3372 VD_OPEN_FLAGS_NORMAL,
3373 NULL, that->mm.vdDiskIfaces);
3374
3375 that->mm.vdProgress = NULL;
3376
3377 if (RT_FAILURE (vrc))
3378 {
3379 throw setError (E_FAIL,
3380 tr ("Could not create the differencing hard disk "
3381 "storage unit '%s'%s"),
3382 targetLocation.raw(), that->vdError (vrc).raw());
3383 }
3384
3385 size = VDGetFileSize (hdd, 0);
3386 logicalSize = VDGetSize (hdd, 0) / _1M;
3387 }
3388 catch (HRESULT aRC) { rc = aRC; }
3389
3390 VDDestroy (hdd);
3391 }
3392 catch (HRESULT aRC) { rc = aRC; }
3393
3394 if (SUCCEEDED (rc))
3395 {
3396 /* we set mParent & children() (note that thatLock is released
3397 * here), but lock VirtualBox first to follow the rule */
3398 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3399 that->treeLock());
3400
3401 Assert (target->mParent.isNull());
3402
3403 /* associate the child with the parent and deassociate from
3404 * VirtualBox */
3405 target->mParent = that;
3406 that->addDependentChild (target);
3407 target->mVirtualBox->removeDependentChild (target);
3408
3409 /* register with mVirtualBox as the last step and move to
3410 * Created state only on success (leaving an orphan file is
3411 * better than breaking media registry consistency) */
3412 rc = that->mVirtualBox->registerHardDisk2 (target);
3413
3414 if (FAILED (rc))
3415 {
3416 /* break the parent association on failure to register */
3417 target->mVirtualBox->addDependentChild (target);
3418 that->removeDependentChild (target);
3419 target->mParent.setNull();
3420 }
3421 }
3422
3423 thatLock.maybeEnter();
3424
3425 if (SUCCEEDED (rc))
3426 {
3427 target->m.state = MediaState_Created;
3428
3429 target->m.size = size;
3430 target->mm.logicalSize = logicalSize;
3431 }
3432 else
3433 {
3434 /* back to NotCreated on failiure */
3435 target->m.state = MediaState_NotCreated;
3436 }
3437
3438 if (isAsync)
3439 {
3440 /* unlock ourselves when done (unless in MediaState_LockedWrite
3441 * state because of taking the online snapshot*/
3442 if (that->m.state != MediaState_LockedWrite)
3443 {
3444 HRESULT rc2 = that->UnlockRead (NULL);
3445 AssertComRC (rc2);
3446 }
3447 }
3448
3449 /* deregister the task registered in createDiffStorage() */
3450 Assert (that->mm.numCreateDiffTasks != 0);
3451 -- that->mm.numCreateDiffTasks;
3452
3453 /* Note that in sync mode, it's the caller's responsibility to
3454 * unlock the hard disk */
3455
3456 break;
3457 }
3458
3459 ////////////////////////////////////////////////////////////////////////
3460
3461 case Task::Merge:
3462 {
3463 /* The lock is also used as a signal from the task initiator (which
3464 * releases it only after RTThreadCreate()) that we can start the
3465 * job. We don't actually need the lock for anything else since the
3466 * object is protected by MediaState_Deleting and we don't modify
3467 * its sensitive fields below */
3468 {
3469 AutoWriteLock thatLock (that);
3470 }
3471
3472 MergeChain *chain = task->d.chain.get();
3473
3474#if 1
3475 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
3476#endif
3477
3478 try
3479 {
3480 PVBOXHDD hdd;
3481 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3482 ComAssertRCThrow (vrc, E_FAIL);
3483
3484 try
3485 {
3486 /* open all hard disks in the chain (they are in the
3487 * {parent,child} order in there. Note that we don't lock
3488 * objects in this chain since they must be in states
3489 * (Deleting and LockedWrite) that prevent from chaning
3490 * their format and location fields from outside. */
3491
3492 for (MergeChain::const_iterator it = chain->begin();
3493 it != chain->end(); ++ it)
3494 {
3495 /* complex sanity (sane complexity) */
3496 Assert ((chain->isForward() &&
3497 ((*it != chain->back() &&
3498 (*it)->m.state == MediaState_Deleting) ||
3499 (*it == chain->back() &&
3500 (*it)->m.state == MediaState_LockedWrite))) ||
3501 (!chain->isForward() &&
3502 ((*it != chain->front() &&
3503 (*it)->m.state == MediaState_Deleting) ||
3504 (*it == chain->front() &&
3505 (*it)->m.state == MediaState_LockedWrite))));
3506
3507 Assert (*it == chain->target() ||
3508 (*it)->m.backRefs.size() == 0);
3509
3510 /* open the first image with VDOPEN_FLAGS_INFO because
3511 * it's not necessarily the base one */
3512 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3513 Utf8Str ((*it)->m.locationFull),
3514 it == chain->begin() ?
3515 VD_OPEN_FLAGS_INFO : 0,
3516 NULL);
3517 if (RT_FAILURE (vrc))
3518 throw vrc;
3519#if 1
3520 LogFlow (("*** MERGE disk = %ls\n",
3521 (*it)->m.locationFull.raw()));
3522#endif
3523 }
3524
3525 /* needed for vdProgressCallback */
3526 that->mm.vdProgress = task->progress;
3527
3528 unsigned start = chain->isForward() ?
3529 0 : chain->size() - 1;
3530 unsigned end = chain->isForward() ?
3531 chain->size() - 1 : 0;
3532#if 1
3533 LogFlow (("*** MERGE from %d to %d\n", start, end));
3534#endif
3535 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
3536
3537 that->mm.vdProgress = NULL;
3538
3539 if (RT_FAILURE (vrc))
3540 throw vrc;
3541
3542 /* update parent UUIDs */
3543 /// @todo VDMerge should be taught to do so, including the
3544 /// multiple children case
3545 if (chain->isForward())
3546 {
3547 /* target's UUID needs to be updated (note that target
3548 * is the only image in the container on success) */
3549 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
3550 if (RT_FAILURE (vrc))
3551 throw vrc;
3552 }
3553 else
3554 {
3555 /* we need to update UUIDs of all source's children
3556 * which cannot be part of the container at once so
3557 * add each one in there individually */
3558 if (chain->children().size() > 0)
3559 {
3560 for (List::const_iterator it = chain->children().begin();
3561 it != chain->children().end(); ++ it)
3562 {
3563 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
3564 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3565 Utf8Str ((*it)->m.locationFull),
3566 VD_OPEN_FLAGS_INFO, NULL);
3567 if (RT_FAILURE (vrc))
3568 throw vrc;
3569
3570 vrc = VDSetParentUuid (hdd, 1,
3571 chain->target()->m.id);
3572 if (RT_FAILURE (vrc))
3573 throw vrc;
3574
3575 vrc = VDClose (hdd, false /* fDelete */);
3576 if (RT_FAILURE (vrc))
3577 throw vrc;
3578 }
3579 }
3580 }
3581 }
3582 catch (HRESULT aRC) { rc = aRC; }
3583 catch (int aVRC)
3584 {
3585 throw setError (E_FAIL,
3586 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
3587 chain->source()->m.locationFull.raw(),
3588 chain->target()->m.locationFull.raw(),
3589 that->vdError (aVRC).raw());
3590 }
3591
3592 VDDestroy (hdd);
3593 }
3594 catch (HRESULT aRC) { rc = aRC; }
3595
3596 HRESULT rc2;
3597
3598 bool saveSettingsFailed = false;
3599
3600 if (SUCCEEDED (rc))
3601 {
3602 /* all hard disks but the target were successfully deleted by
3603 * VDMerge; reparent the last one and uninitialize deleted */
3604
3605 /* we set mParent & children() (note that thatLock is released
3606 * here), but lock VirtualBox first to follow the rule */
3607 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3608 that->treeLock());
3609
3610 HardDisk2 *source = chain->source();
3611 HardDisk2 *target = chain->target();
3612
3613 if (chain->isForward())
3614 {
3615 /* first, unregister the target since it may become a base
3616 * hard disk which needs re-registration */
3617 rc2 = target->mVirtualBox->
3618 unregisterHardDisk2 (target, false /* aSaveSettings */);
3619 AssertComRC (rc2);
3620
3621 /* then, reparent it and disconnect the deleted branch at
3622 * both ends (chain->parent() is source's parent) */
3623 target->mParent->removeDependentChild (target);
3624 target->mParent = chain->parent();
3625 if (!target->mParent.isNull())
3626 {
3627 target->mParent->addDependentChild (target);
3628 target->mParent->removeDependentChild (source);
3629 source->mParent.setNull();
3630 }
3631 else
3632 {
3633 target->mVirtualBox->addDependentChild (target);
3634 target->mVirtualBox->removeDependentChild (source);
3635 }
3636
3637 /* then, register again */
3638 rc2 = target->mVirtualBox->
3639 registerHardDisk2 (target, false /* aSaveSettings */);
3640 AssertComRC (rc2);
3641 }
3642 else
3643 {
3644 Assert (target->children().size() == 1);
3645 HardDisk2 *targetChild = target->children().front();
3646
3647 /* disconnect the deleted branch at the elder end */
3648 target->removeDependentChild (targetChild);
3649 targetChild->mParent.setNull();
3650
3651 const List &children = chain->children();
3652
3653 /* reparent source's chidren and disconnect the deleted
3654 * branch at the younger end m*/
3655 if (children.size() > 0)
3656 {
3657 /* obey {parent,child} lock order */
3658 AutoWriteLock sourceLock (source);
3659
3660 for (List::const_iterator it = children.begin();
3661 it != children.end(); ++ it)
3662 {
3663 AutoWriteLock childLock (*it);
3664
3665 (*it)->mParent = target;
3666 (*it)->mParent->addDependentChild (*it);
3667 source->removeDependentChild (*it);
3668 }
3669 }
3670 }
3671
3672 /* try to save the hard disk registry */
3673 rc = that->mVirtualBox->saveSettings();
3674
3675 if (SUCCEEDED (rc))
3676 {
3677 /* unregister and uninitialize all hard disks in the chain
3678 * but the target */
3679
3680 for (MergeChain::iterator it = chain->begin();
3681 it != chain->end();)
3682 {
3683 if (*it == chain->target())
3684 {
3685 ++ it;
3686 continue;
3687 }
3688
3689 rc2 = (*it)->mVirtualBox->
3690 unregisterHardDisk2 (*it, false /* aSaveSettings */);
3691 AssertComRC (rc2);
3692
3693 /* now, uninitialize the deleted hard disk (note that
3694 * due to the Deleting state, uninit() will not touch
3695 * the parent-child relationship so we need to
3696 * uninitialize each disk individually) */
3697
3698 /* note that the operation initiator hard disk (which is
3699 * normally also the source hard disk) is a special case
3700 * -- there is one more caller added by Task to it which
3701 * we must release. Also, if we are in sync mode, the
3702 * caller may still hold an AutoCaller instance for it
3703 * and therefore we cannot uninit() it (it's therefore
3704 * the caller's responsibility) */
3705 if (*it == that)
3706 task->autoCaller.release();
3707
3708 /* release the caller added by MergeChain before
3709 * uninit() */
3710 (*it)->releaseCaller();
3711
3712 if (isAsync || *it != that)
3713 (*it)->uninit();
3714
3715 /* delete (to prevent uninitialization in MergeChain
3716 * dtor) and advance to the next item */
3717 it = chain->erase (it);
3718 }
3719
3720 /* Note that states of all other hard disks (target, parent,
3721 * children) will be restored by the MergeChain dtor */
3722 }
3723 else
3724 {
3725 /* too bad if we fail, but we'll need to rollback everything
3726 * we did above to at least keep the HD tree in sync with
3727 * the current registry on disk */
3728
3729 saveSettingsFailed = true;
3730
3731 /// @todo NEWMEDIA implement a proper undo
3732
3733 AssertFailed();
3734 }
3735 }
3736
3737 if (FAILED (rc))
3738 {
3739 /* Here we come if either VDMerge() failed (in which case we
3740 * assume that it tried to do everything to make a further
3741 * retry possible -- e.g. not deleted intermediate hard disks
3742 * and so on) or VirtualBox::saveSettings() failed (where we
3743 * should have the original tree but with intermediate storage
3744 * units deleted by VDMerge()). We have to only restore states
3745 * (through the MergeChain dtor) unless we are run synchronously
3746 * in which case it's the responsibility of the caller as stated
3747 * in the mergeTo() docs. The latter also implies that we
3748 * don't own the merge chain, so release it in this case. */
3749
3750 if (!isAsync)
3751 task->d.chain.release();
3752
3753 NOREF (saveSettingsFailed);
3754 }
3755
3756 break;
3757 }
3758
3759 ////////////////////////////////////////////////////////////////////////
3760
3761 case Task::Delete:
3762 {
3763 /* The lock is also used as a signal from the task initiator (which
3764 * releases it only after RTThreadCreate()) that we can start the job */
3765 AutoWriteLock thatLock (that);
3766
3767 try
3768 {
3769 PVBOXHDD hdd;
3770 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3771 ComAssertRCThrow (vrc, E_FAIL);
3772
3773 Utf8Str format (that->mm.format);
3774 Utf8Str location (that->m.locationFull);
3775
3776 /* unlock before the potentially lengthy operation */
3777 Assert (that->m.state == MediaState_Deleting);
3778 thatLock.leave();
3779
3780 try
3781 {
3782 vrc = VDOpen (hdd, format, location,
3783 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3784 NULL);
3785 if (RT_SUCCESS (vrc))
3786 vrc = VDClose (hdd, true /* fDelete */);
3787
3788 if (RT_FAILURE (vrc))
3789 {
3790 throw setError (E_FAIL,
3791 tr ("Could not delete the hard disk storage "
3792 "unit '%s'%s"),
3793 location.raw(), that->vdError (vrc).raw());
3794 }
3795
3796 }
3797 catch (HRESULT aRC) { rc = aRC; }
3798
3799 VDDestroy (hdd);
3800 }
3801 catch (HRESULT aRC) { rc = aRC; }
3802
3803 thatLock.maybeEnter();
3804
3805 /* go to the NotCreated state even on failure since the storage
3806 * may have been already partially deleted and cannot be used any
3807 * more. One will be able to manually re-open the storage if really
3808 * needed to re-register it. */
3809 that->m.state = MediaState_NotCreated;
3810
3811 /* Reset UUID to prevent Create* from reusing it again */
3812 unconst (that->m.id).clear();
3813
3814 break;
3815 }
3816
3817 default:
3818 AssertFailedReturn (VERR_GENERAL_FAILURE);
3819 }
3820
3821 /* complete the progress if run asynchronously */
3822 if (isAsync)
3823 {
3824 if (!task->progress.isNull())
3825 task->progress->notifyComplete (rc);
3826 }
3827 else
3828 {
3829 task->rc = rc;
3830 }
3831
3832 LogFlowFunc (("rc=%Rhrc\n", rc));
3833 LogFlowFuncLeave();
3834
3835 return VINF_SUCCESS;
3836
3837 /// @todo ugly hack, fix ComAssert... later
3838 #undef setError
3839}
3840/* 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