VirtualBox

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

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

Main: Fixed %ls/%s and Utf8Str/Bstr mismatch in hard disk code.

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