VirtualBox

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

Last change on this file since 15191 was 15124, checked in by vboxsync, 16 years ago

Main: Only log full media location (to reduce logging).

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

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