VirtualBox

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

Last change on this file since 16596 was 16577, checked in by vboxsync, 16 years ago

Main: fix debug assertion to no longer fail if an ordinary error had already been reported

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

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