VirtualBox

source: vbox/trunk/src/VBox/Main/HardDiskImpl.cpp@ 19610

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

Main-OVF: make sure the source chain is released early on disk image clone (prevents death locks on OVF import of the SUSS)

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

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