VirtualBox

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

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

API: weed out NULL strings, as many clients cannot use them

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