VirtualBox

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

Last change on this file since 16560 was 16560, checked in by vboxsync, 15 years ago

Main: do not include include/VBox/settings.h from other header files but only from implementations that need it (save compile time)

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

© 2023 Oracle
ContactPrivacy policyTerms of Use