VirtualBox

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

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

Main: coding style: have Main obey the standard VirtualBox coding style rules (no functional changes)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 159.1 KB
Line 
1/* $Id: HardDiskImpl.cpp 21878 2009-07-30 12:42:08Z 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 has more than one child hard disk (%d)"),
426 aHardDisk->m.locationFull.raw(),
427 aHardDisk->children().size());
428 }
429 }
430
431 if (aAttachments && !mIgnoreAttachments)
432 {
433 if (aHardDisk->m.backRefs.size() != 0)
434 return setError (E_FAIL,
435 tr ("Hard disk '%ls' is attached to %d virtual machines"),
436 aHardDisk->m.locationFull.raw(),
437 aHardDisk->m.backRefs.size());
438 }
439
440 if (aImmutable)
441 {
442 if (aHardDisk->mm.type == HardDiskType_Immutable)
443 return setError (E_FAIL,
444 tr ("Hard disk '%ls' is immutable"),
445 aHardDisk->m.locationFull.raw());
446 }
447
448 return S_OK;
449 }
450
451 HRESULT checkChildren (HardDisk *aHardDisk)
452 { return check (aHardDisk, true, false, false); }
453
454 HRESULT checkChildrenAndImmutable (HardDisk *aHardDisk)
455 { return check (aHardDisk, true, false, true); }
456
457 HRESULT checkChildrenAndAttachments (HardDisk *aHardDisk)
458 { return check (aHardDisk, true, true, false); }
459
460 HRESULT checkChildrenAndAttachmentsAndImmutable (HardDisk *aHardDisk)
461 { return check (aHardDisk, true, true, true); }
462
463 /** true if forward merge, false if backward */
464 bool mForward : 1;
465 /** true to not perform attachment checks */
466 bool mIgnoreAttachments : 1;
467
468 /** Parent of the source when forward merge (if any) */
469 ComObjPtr<HardDisk> mParent;
470 /** Children of the source when backward merge (if any) */
471 List mChildren;
472};
473
474////////////////////////////////////////////////////////////////////////////////
475
476/**
477 * Helper class for image operations involving the entire parent chain.
478 *
479 * @note It is assumed that when modifying methods of this class are called,
480 * HardDisk::treeLock() is held in read mode.
481 */
482class HardDisk::ImageChain : public HardDisk::List,
483 public com::SupportErrorInfoBase
484{
485public:
486
487 ImageChain () {}
488
489 ~ImageChain()
490 {
491 /* empty? */
492 if (begin() != end())
493 {
494 List::const_iterator last = end();
495 last--;
496 for (List::const_iterator it = begin(); it != end(); ++ it)
497 {
498 AutoWriteLock alock(*it);
499 if (it == last)
500 {
501 Assert ( (*it)->m.state == MediaState_LockedRead
502 || (*it)->m.state == MediaState_LockedWrite);
503 if ((*it)->m.state == MediaState_LockedRead)
504 (*it)->UnlockRead (NULL);
505 else if ((*it)->m.state == MediaState_LockedWrite)
506 (*it)->UnlockWrite (NULL);
507 }
508 else
509 {
510 Assert ((*it)->m.state == MediaState_LockedRead);
511 if ((*it)->m.state == MediaState_LockedRead)
512 (*it)->UnlockRead (NULL);
513 }
514
515 (*it)->releaseCaller();
516 }
517 }
518 }
519
520 HRESULT addImage (HardDisk *aHardDisk)
521 {
522 HRESULT rc = aHardDisk->addCaller();
523 CheckComRCReturnRC(rc);
524
525 push_front (aHardDisk);
526
527 return S_OK;
528 }
529
530 HRESULT lockImagesRead ()
531 {
532 /* Lock all disks in the chain in {parent, child} order,
533 * and make sure they are accessible. */
534 /// @todo code duplication with SessionMachine::lockMedia, see below
535 ErrorInfoKeeper eik (true /* aIsNull */);
536 MultiResult mrc (S_OK);
537 for (List::const_iterator it = begin(); it != end(); ++ it)
538 {
539 HRESULT rc = S_OK;
540 MediaState_T mediaState;
541 rc = (*it)->LockRead(&mediaState);
542 CheckComRCReturnRC(rc);
543
544 if (mediaState == MediaState_Inaccessible)
545 {
546 rc = (*it)->COMGETTER(State) (&mediaState);
547 CheckComRCReturnRC(rc);
548 Assert (mediaState == MediaState_LockedRead);
549
550 /* Note that we locked the medium already, so use the error
551 * value to see if there was an accessibility failure */
552 Bstr error;
553 rc = (*it)->COMGETTER(LastAccessError) (error.asOutParam());
554 CheckComRCReturnRC(rc);
555
556 if (!error.isEmpty())
557 {
558 Bstr loc;
559 rc = (*it)->COMGETTER(Location) (loc.asOutParam());
560 CheckComRCThrowRC (rc);
561
562 /* collect multiple errors */
563 eik.restore();
564
565 /* be in sync with MediumBase::setStateError() */
566 Assert (!error.isEmpty());
567 mrc = setError (E_FAIL,
568 tr ("Medium '%ls' is not accessible. %ls"),
569 loc.raw(), error.raw());
570
571 eik.fetch();
572 }
573 }
574 }
575
576 eik.restore();
577 CheckComRCReturnRC((HRESULT) mrc);
578
579 return S_OK;
580 }
581
582 HRESULT lockImagesReadAndLastWrite ()
583 {
584 /* Lock all disks in the chain in {parent, child} order,
585 * and make sure they are accessible. */
586 /// @todo code duplication with SessionMachine::lockMedia, see below
587 ErrorInfoKeeper eik (true /* aIsNull */);
588 MultiResult mrc (S_OK);
589 List::const_iterator last = end();
590 last--;
591 for (List::const_iterator it = begin(); it != end(); ++ it)
592 {
593 HRESULT rc = S_OK;
594 MediaState_T mediaState;
595 if (it == last)
596 rc = (*it)->LockWrite(&mediaState);
597 else
598 rc = (*it)->LockRead(&mediaState);
599 CheckComRCReturnRC(rc);
600
601 if (mediaState == MediaState_Inaccessible)
602 {
603 rc = (*it)->COMGETTER(State) (&mediaState);
604 CheckComRCReturnRC(rc);
605 if (it == last)
606 Assert (mediaState == MediaState_LockedWrite);
607 else
608 Assert (mediaState == MediaState_LockedRead);
609
610 /* Note that we locked the medium already, so use the error
611 * value to see if there was an accessibility failure */
612 Bstr error;
613 rc = (*it)->COMGETTER(LastAccessError) (error.asOutParam());
614 CheckComRCReturnRC(rc);
615
616 if (!error.isEmpty())
617 {
618 Bstr loc;
619 rc = (*it)->COMGETTER(Location) (loc.asOutParam());
620 CheckComRCThrowRC (rc);
621
622 /* collect multiple errors */
623 eik.restore();
624
625 /* be in sync with MediumBase::setStateError() */
626 Assert (!error.isEmpty());
627 mrc = setError (E_FAIL,
628 tr ("Medium '%ls' is not accessible. %ls"),
629 loc.raw(), error.raw());
630
631 eik.fetch();
632 }
633 }
634 }
635
636 eik.restore();
637 CheckComRCReturnRC((HRESULT) mrc);
638
639 return S_OK;
640 }
641
642protected:
643
644 // SupportErrorInfoBase interface
645 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk); }
646 const char *componentName() const { return HardDisk::ComponentName(); }
647
648private:
649
650};
651
652////////////////////////////////////////////////////////////////////////////////
653// HardDisk class
654////////////////////////////////////////////////////////////////////////////////
655
656// constructor / destructor
657////////////////////////////////////////////////////////////////////////////////
658
659DEFINE_EMPTY_CTOR_DTOR (HardDisk)
660
661HRESULT HardDisk::FinalConstruct()
662{
663 /* Initialize the callbacks of the VD error interface */
664 mm.vdIfCallsError.cbSize = sizeof (VDINTERFACEERROR);
665 mm.vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
666 mm.vdIfCallsError.pfnError = vdErrorCall;
667 mm.vdIfCallsError.pfnMessage = NULL;
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 creation"),
1462 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 creation"),
1467 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 separate out creating/locking an image chain from
1614 * SessionMachine::lockMedia and use it from here too.
1615 * logically this belongs into HardDisk functionality. */
1616
1617 /* Build the source chain and lock images in the proper order. */
1618 std::auto_ptr <ImageChain> srcChain (new ImageChain ());
1619
1620 /* we walk the source tree */
1621 AutoReadLock srcTreeLock (this->treeLock());
1622 for (HardDisk *hd = this; hd; hd = hd->mParent)
1623 {
1624 rc = srcChain->addImage(hd);
1625 CheckComRCThrowRC (rc);
1626 }
1627 rc = srcChain->lockImagesRead();
1628 CheckComRCThrowRC (rc);
1629
1630 /* Build the parent chain and lock images in the proper order. */
1631 std::auto_ptr <ImageChain> parentChain (new ImageChain ());
1632
1633 /* we walk the future parent tree */
1634 AutoReadLock parentTreeLock;
1635 if (parent)
1636 parentTreeLock.attach(parent->treeLock());
1637 for (HardDisk *hd = parent; hd; hd = hd->mParent)
1638 {
1639 rc = parentChain->addImage(hd);
1640 CheckComRCThrowRC (rc);
1641 }
1642 if (target->m.state == MediaState_Created)
1643 {
1644 /* If we're cloning to an existing image the parent chain also
1645 * contains the target image, and it gets locked for writing. */
1646 rc = parentChain->addImage(target);
1647 CheckComRCThrowRC (rc);
1648 rc = parentChain->lockImagesReadAndLastWrite();
1649 CheckComRCThrowRC (rc);
1650 }
1651 else
1652 {
1653 rc = parentChain->lockImagesRead();
1654 CheckComRCThrowRC (rc);
1655 }
1656
1657 progress.createObject();
1658 rc = progress->init (mVirtualBox, static_cast <IHardDisk *> (this),
1659 BstrFmt (tr ("Creating clone hard disk '%ls'"),
1660 target->m.locationFull.raw()),
1661 TRUE /* aCancelable */);
1662 CheckComRCThrowRC (rc);
1663
1664 /* setup task object and thread to carry out the operation
1665 * asynchronously */
1666
1667 std::auto_ptr <Task> task (new Task (this, progress, Task::Clone));
1668 AssertComRCThrowRC (task->autoCaller.rc());
1669
1670 task->setData (target, parent);
1671 task->d.variant = aVariant;
1672 task->setData (srcChain.release(), parentChain.release());
1673
1674 rc = task->startThread();
1675 CheckComRCThrowRC (rc);
1676
1677 if (target->m.state == MediaState_NotCreated)
1678 {
1679 /* go to Creating state before leaving the lock */
1680 target->m.state = MediaState_Creating;
1681 }
1682
1683 /* task is now owned (or already deleted) by taskThread() so release it */
1684 task.release();
1685 }
1686 catch (HRESULT aRC)
1687 {
1688 rc = aRC;
1689 }
1690
1691 if (SUCCEEDED(rc))
1692 {
1693 /* return progress to the caller */
1694 progress.queryInterfaceTo(aProgress);
1695 }
1696
1697 return rc;
1698}
1699
1700STDMETHODIMP HardDisk::Compact (IProgress **aProgress)
1701{
1702 CheckComArgOutPointerValid(aProgress);
1703
1704 AutoCaller autoCaller(this);
1705 CheckComRCReturnRC(autoCaller.rc());
1706
1707 AutoWriteLock alock(this);
1708
1709 ComObjPtr<Progress> progress;
1710
1711 HRESULT rc = S_OK;
1712
1713 try
1714 {
1715 /** @todo separate out creating/locking an image chain from
1716 * SessionMachine::lockMedia and use it from here too.
1717 * logically this belongs into HardDisk functionality. */
1718
1719 /* Build the image chain and lock images in the proper order. */
1720 std::auto_ptr <ImageChain> imgChain (new ImageChain ());
1721
1722 /* we walk the image tree */
1723 AutoReadLock srcTreeLock (this->treeLock());
1724 for (HardDisk *hd = this; hd; hd = hd->mParent)
1725 {
1726 rc = imgChain->addImage(hd);
1727 CheckComRCThrowRC (rc);
1728 }
1729 rc = imgChain->lockImagesReadAndLastWrite();
1730 CheckComRCThrowRC (rc);
1731
1732 progress.createObject();
1733 rc = progress->init (mVirtualBox, static_cast <IHardDisk *> (this),
1734 BstrFmt (tr ("Compacting hard disk '%ls'"), m.locationFull.raw()),
1735 TRUE /* aCancelable */);
1736 CheckComRCThrowRC (rc);
1737
1738 /* setup task object and thread to carry out the operation
1739 * asynchronously */
1740
1741 std::auto_ptr <Task> task (new Task (this, progress, Task::Compact));
1742 AssertComRCThrowRC (task->autoCaller.rc());
1743
1744 task->setData (imgChain.release());
1745
1746 rc = task->startThread();
1747 CheckComRCThrowRC (rc);
1748
1749 /* task is now owned (or already deleted) by taskThread() so release it */
1750 task.release();
1751 }
1752 catch (HRESULT aRC)
1753 {
1754 rc = aRC;
1755 }
1756
1757 if (SUCCEEDED(rc))
1758 {
1759 /* return progress to the caller */
1760 progress.queryInterfaceTo(aProgress);
1761 }
1762
1763 return rc;
1764}
1765
1766STDMETHODIMP HardDisk::Reset (IProgress **aProgress)
1767{
1768 CheckComArgOutPointerValid(aProgress);
1769
1770 AutoCaller autoCaller(this);
1771 CheckComRCReturnRC(autoCaller.rc());
1772
1773 AutoWriteLock alock(this);
1774
1775 if (mParent.isNull())
1776 return setError (VBOX_E_NOT_SUPPORTED,
1777 tr ("Hard disk '%ls' is not differencing"),
1778 m.locationFull.raw());
1779
1780 HRESULT rc = canClose();
1781 CheckComRCReturnRC(rc);
1782
1783 rc = LockWrite (NULL);
1784 CheckComRCReturnRC(rc);
1785
1786 ComObjPtr<Progress> progress;
1787
1788 try
1789 {
1790 progress.createObject();
1791 rc = progress->init (mVirtualBox, static_cast <IHardDisk *> (this),
1792 BstrFmt (tr ("Resetting differencing hard disk '%ls'"),
1793 m.locationFull.raw()),
1794 FALSE /* aCancelable */);
1795 CheckComRCThrowRC (rc);
1796
1797 /* setup task object and thread to carry out the operation
1798 * asynchronously */
1799
1800 std::auto_ptr <Task> task (new Task (this, progress, Task::Reset));
1801 AssertComRCThrowRC (task->autoCaller.rc());
1802
1803 rc = task->startThread();
1804 CheckComRCThrowRC (rc);
1805
1806 /* task is now owned (or already deleted) by taskThread() so release it */
1807 task.release();
1808 }
1809 catch (HRESULT aRC)
1810 {
1811 rc = aRC;
1812 }
1813
1814 if (FAILED (rc))
1815 {
1816 HRESULT rc2 = UnlockWrite (NULL);
1817 AssertComRC (rc2);
1818 /* Note: on success, taskThread() will unlock this */
1819 }
1820 else
1821 {
1822 /* return progress to the caller */
1823 progress.queryInterfaceTo(aProgress);
1824 }
1825
1826 return rc;
1827}
1828
1829// public methods for internal purposes only
1830////////////////////////////////////////////////////////////////////////////////
1831
1832/**
1833 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
1834 * of this hard disk or any its child and updates the paths if necessary to
1835 * reflect the new location.
1836 *
1837 * @param aOldPath Old path (full).
1838 * @param aNewPath New path (full).
1839 *
1840 * @note Locks treeLock() for reading, this object and all children for writing.
1841 */
1842void HardDisk::updatePaths (const char *aOldPath, const char *aNewPath)
1843{
1844 AssertReturnVoid (aOldPath);
1845 AssertReturnVoid (aNewPath);
1846
1847 AutoCaller autoCaller(this);
1848 AssertComRCReturnVoid (autoCaller.rc());
1849
1850 AutoWriteLock alock(this);
1851
1852 /* we access children() */
1853 AutoReadLock treeLock (this->treeLock());
1854
1855 updatePath (aOldPath, aNewPath);
1856
1857 /* update paths of all children */
1858 for (List::const_iterator it = children().begin();
1859 it != children().end();
1860 ++ it)
1861 {
1862 (*it)->updatePaths (aOldPath, aNewPath);
1863 }
1864}
1865
1866/**
1867 * Returns the base hard disk of the hard disk chain this hard disk is part of.
1868 *
1869 * The root hard disk is found by walking up the parent-child relationship axis.
1870 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
1871 * returns itself in response to this method.
1872 *
1873 * @param aLevel Where to store the number of ancestors of this hard disk
1874 * (zero for the root), may be @c NULL.
1875 *
1876 * @note Locks treeLock() for reading.
1877 */
1878ComObjPtr<HardDisk> HardDisk::root (uint32_t *aLevel /*= NULL*/)
1879{
1880 ComObjPtr<HardDisk> root;
1881 uint32_t level;
1882
1883 AutoCaller autoCaller(this);
1884 AssertReturn(autoCaller.isOk(), root);
1885
1886 /* we access mParent */
1887 AutoReadLock treeLock (this->treeLock());
1888
1889 root = this;
1890 level = 0;
1891
1892 if (!mParent.isNull())
1893 {
1894 for (;;)
1895 {
1896 AutoCaller rootCaller (root);
1897 AssertReturn(rootCaller.isOk(), root);
1898
1899 if (root->mParent.isNull())
1900 break;
1901
1902 root = root->mParent;
1903 ++ level;
1904 }
1905 }
1906
1907 if (aLevel != NULL)
1908 *aLevel = level;
1909
1910 return root;
1911}
1912
1913/**
1914 * Returns @c true if this hard disk cannot be modified because it has
1915 * dependants (children) or is part of the snapshot. Related to the hard disk
1916 * type and posterity, not to the current media state.
1917 *
1918 * @note Locks this object and treeLock() for reading.
1919 */
1920bool HardDisk::isReadOnly()
1921{
1922 AutoCaller autoCaller(this);
1923 AssertComRCReturn (autoCaller.rc(), false);
1924
1925 AutoReadLock alock(this);
1926
1927 /* we access children */
1928 AutoReadLock treeLock (this->treeLock());
1929
1930 switch (mm.type)
1931 {
1932 case HardDiskType_Normal:
1933 {
1934 if (children().size() != 0)
1935 return true;
1936
1937 for (BackRefList::const_iterator it = m.backRefs.begin();
1938 it != m.backRefs.end(); ++ it)
1939 if (it->snapshotIds.size() != 0)
1940 return true;
1941
1942 return false;
1943 }
1944 case HardDiskType_Immutable:
1945 {
1946 return true;
1947 }
1948 case HardDiskType_Writethrough:
1949 {
1950 return false;
1951 }
1952 default:
1953 break;
1954 }
1955
1956 AssertFailedReturn (false);
1957}
1958
1959/**
1960 * Saves hard disk data by appending a new <HardDisk> child node to the given
1961 * parent node which can be either <HardDisks> or <HardDisk>.
1962 *
1963 * @param aaParentNode Parent <HardDisks> or <HardDisk> node.
1964 *
1965 * @note Locks this object, treeLock() and children for reading.
1966 */
1967HRESULT HardDisk::saveSettings (settings::Key &aParentNode)
1968{
1969 using namespace settings;
1970
1971 AssertReturn(!aParentNode.isNull(), E_FAIL);
1972
1973 AutoCaller autoCaller(this);
1974 CheckComRCReturnRC(autoCaller.rc());
1975
1976 AutoReadLock alock(this);
1977
1978 /* we access mParent */
1979 AutoReadLock treeLock (this->treeLock());
1980
1981 Key diskNode = aParentNode.appendKey ("HardDisk");
1982 /* required */
1983 diskNode.setValue <Guid> ("uuid", m.id);
1984 /* required (note: the original locaiton, not full) */
1985 diskNode.setValue <Bstr> ("location", m.location);
1986 /* required */
1987 diskNode.setValue <Bstr> ("format", mm.format);
1988 /* optional, only for diffs, default is false */
1989 if (!mParent.isNull())
1990 diskNode.setValueOr <bool> ("autoReset", !!mm.autoReset, false);
1991 /* optional */
1992 if (!m.description.isNull())
1993 {
1994 Key descNode = diskNode.createKey ("Description");
1995 descNode.setKeyValue <Bstr> (m.description);
1996 }
1997
1998 /* optional properties */
1999 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
2000 it != mm.properties.end(); ++ it)
2001 {
2002 /* only save properties that have non-default values */
2003 if (!it->second.isNull())
2004 {
2005 Key propNode = diskNode.appendKey ("Property");
2006 propNode.setValue <Bstr> ("name", it->first);
2007 propNode.setValue <Bstr> ("value", it->second);
2008 }
2009 }
2010
2011 /* only for base hard disks */
2012 if (mParent.isNull())
2013 {
2014 const char *type =
2015 mm.type == HardDiskType_Normal ? "Normal" :
2016 mm.type == HardDiskType_Immutable ? "Immutable" :
2017 mm.type == HardDiskType_Writethrough ? "Writethrough" : NULL;
2018 Assert (type != NULL);
2019 diskNode.setStringValue ("type", type);
2020 }
2021
2022 /* save all children */
2023 for (List::const_iterator it = children().begin();
2024 it != children().end();
2025 ++ it)
2026 {
2027 HRESULT rc = (*it)->saveSettings (diskNode);
2028 AssertComRCReturnRC(rc);
2029 }
2030
2031 return S_OK;
2032}
2033
2034/**
2035 * Compares the location of this hard disk to the given location.
2036 *
2037 * The comparison takes the location details into account. For example, if the
2038 * location is a file in the host's filesystem, a case insensitive comparison
2039 * will be performed for case insensitive filesystems.
2040 *
2041 * @param aLocation Location to compare to (as is).
2042 * @param aResult Where to store the result of comparison: 0 if locations
2043 * are equal, 1 if this object's location is greater than
2044 * the specified location, and -1 otherwise.
2045 */
2046HRESULT HardDisk::compareLocationTo (const char *aLocation, int &aResult)
2047{
2048 AutoCaller autoCaller(this);
2049 AssertComRCReturnRC(autoCaller.rc());
2050
2051 AutoReadLock alock(this);
2052
2053 Utf8Str locationFull (m.locationFull);
2054
2055 /// @todo NEWMEDIA delegate the comparison to the backend?
2056
2057 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
2058 {
2059 Utf8Str location (aLocation);
2060
2061 /* For locations represented by files, append the default path if
2062 * only the name is given, and then get the full path. */
2063 if (!RTPathHavePath (aLocation))
2064 {
2065 AutoReadLock propsLock (mVirtualBox->systemProperties());
2066 location = Utf8StrFmt ("%ls%c%s",
2067 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2068 RTPATH_DELIMITER, aLocation);
2069 }
2070
2071 int vrc = mVirtualBox->calculateFullPath (location, location);
2072 if (RT_FAILURE(vrc))
2073 return setError (E_FAIL,
2074 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2075 location.raw(), vrc);
2076
2077 aResult = RTPathCompare (locationFull, location);
2078 }
2079 else
2080 aResult = locationFull.compare (aLocation);
2081
2082 return S_OK;
2083}
2084
2085/**
2086 * Returns a short version of the location attribute.
2087 *
2088 * Reimplements MediumBase::name() to specially treat non-FS-path locations.
2089 *
2090 * @note Must be called from under this object's read or write lock.
2091 */
2092Utf8Str HardDisk::name()
2093{
2094 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to requiest
2095 /// this information from the VD backend)
2096
2097 Utf8Str location (m.locationFull);
2098
2099 Utf8Str name = RTPathFilename (location);
2100 return name;
2101}
2102
2103/**
2104 * Checks that this hard disk may be discarded and performs necessary state
2105 * changes.
2106 *
2107 * This method is to be called prior to calling the #discard() to perform
2108 * necessary consistency checks and place involved hard disks to appropriate
2109 * states. If #discard() is not called or fails, the state modifications
2110 * performed by this method must be undone by #cancelDiscard().
2111 *
2112 * See #discard() for more info about discarding hard disks.
2113 *
2114 * @param aChain Where to store the created merge chain (may return NULL
2115 * if no real merge is necessary).
2116 *
2117 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2118 * intermediate hard disks for writing.
2119 */
2120HRESULT HardDisk::prepareDiscard (MergeChain * &aChain)
2121{
2122 AutoCaller autoCaller(this);
2123 AssertComRCReturnRC(autoCaller.rc());
2124
2125 aChain = NULL;
2126
2127 AutoWriteLock alock(this);
2128
2129 /* we access mParent & children() */
2130 AutoReadLock treeLock (this->treeLock());
2131
2132 AssertReturn(mm.type == HardDiskType_Normal, E_FAIL);
2133
2134 if (children().size() == 0)
2135 {
2136 /* special treatment of the last hard disk in the chain: */
2137
2138 if (mParent.isNull())
2139 {
2140 /* lock only, to prevent any usage; discard() will unlock */
2141 return LockWrite (NULL);
2142 }
2143
2144 /* the differencing hard disk w/o children will be deleted, protect it
2145 * from attaching to other VMs (this is why Deleting) */
2146
2147 switch (m.state)
2148 {
2149 case MediaState_Created:
2150 m.state = MediaState_Deleting;
2151 break;
2152 default:
2153 return setStateError();
2154 }
2155
2156 /* aChain is intentionally NULL here */
2157
2158 return S_OK;
2159 }
2160
2161 /* not going multi-merge as it's too expensive */
2162 if (children().size() > 1)
2163 return setError (E_FAIL,
2164 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
2165 m.locationFull.raw(), children().size());
2166
2167 /* this is a read-only hard disk with children; it must be associated with
2168 * exactly one snapshot (when the snapshot is being taken, none of the
2169 * current VM's hard disks may be attached to other VMs). Note that by the
2170 * time when discard() is called, there must be no any attachments at all
2171 * (the code calling prepareDiscard() should detach). */
2172 AssertReturn(m.backRefs.size() == 1 &&
2173 !m.backRefs.front().inCurState &&
2174 m.backRefs.front().snapshotIds.size() == 1, E_FAIL);
2175
2176 ComObjPtr<HardDisk> child = children().front();
2177
2178 /* we keep this locked, so lock the affected child to make sure the lock
2179 * order is correct when calling prepareMergeTo() */
2180 AutoWriteLock childLock (child);
2181
2182 /* delegate the rest to the profi */
2183 if (mParent.isNull())
2184 {
2185 /* base hard disk, backward merge */
2186
2187 Assert (child->m.backRefs.size() == 1);
2188 if (child->m.backRefs.front().machineId != m.backRefs.front().machineId)
2189 {
2190 /* backward merge is too tricky, we'll just detach on discard, so
2191 * lock only, to prevent any usage; discard() will only unlock
2192 * (since we return NULL in aChain) */
2193 return LockWrite (NULL);
2194 }
2195
2196 return child->prepareMergeTo (this, aChain,
2197 true /* aIgnoreAttachments */);
2198 }
2199 else
2200 {
2201 /* forward merge */
2202 return prepareMergeTo (child, aChain,
2203 true /* aIgnoreAttachments */);
2204 }
2205}
2206
2207/**
2208 * Discards this hard disk.
2209 *
2210 * Discarding the hard disk is merging its contents to its differencing child
2211 * hard disk (forward merge) or contents of its child hard disk to itself
2212 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
2213 * a differencing hard disk w/o children, then it will be simply deleted.
2214 * Calling this method on a base hard disk w/o children will do nothing and
2215 * silently succeed. If this hard disk has more than one child, the method will
2216 * currently return an error (since merging in this case would be too expensive
2217 * and result in data duplication).
2218 *
2219 * When the backward merge takes place (i.e. this hard disk is a target) then,
2220 * on success, this hard disk will automatically replace the differencing child
2221 * hard disk used as a source (which will then be deleted) in the attachment
2222 * this child hard disk is associated with. This will happen only if both hard
2223 * disks belong to the same machine because otherwise such a replace would be
2224 * too tricky and could be not expected by the other machine. Same relates to a
2225 * case when the child hard disk is not associated with any machine at all. When
2226 * the backward merge is not applied, the method behaves as if the base hard
2227 * disk were not attached at all -- i.e. simply detaches it from the machine but
2228 * leaves the hard disk chain intact.
2229 *
2230 * This method is basically a wrapper around #mergeTo() that selects the correct
2231 * merge direction and performs additional actions as described above and.
2232 *
2233 * Note that this method will not return until the merge operation is complete
2234 * (which may be quite time consuming depending on the size of the merged hard
2235 * disks).
2236 *
2237 * Note that #prepareDiscard() must be called before calling this method. If
2238 * this method returns a failure, the caller must call #cancelDiscard(). On
2239 * success, #cancelDiscard() must not be called (this method will perform all
2240 * necessary steps such as resetting states of all involved hard disks and
2241 * deleting @a aChain).
2242 *
2243 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
2244 * no real merge takes place).
2245 *
2246 * @note Locks the hard disks from the chain for writing. Locks the machine
2247 * object when the backward merge takes place. Locks treeLock() lock for
2248 * reading or writing.
2249 */
2250HRESULT HardDisk::discard (ComObjPtr<Progress> &aProgress, MergeChain *aChain)
2251{
2252 AssertReturn(!aProgress.isNull(), E_FAIL);
2253
2254 ComObjPtr<HardDisk> hdFrom;
2255
2256 HRESULT rc = S_OK;
2257
2258 {
2259 AutoCaller autoCaller(this);
2260 AssertComRCReturnRC(autoCaller.rc());
2261
2262 aProgress->setNextOperation(BstrFmt(tr("Discarding hard disk '%s'"), name().raw()),
2263 1); // weight
2264
2265 if (aChain == NULL)
2266 {
2267 AutoWriteLock alock(this);
2268
2269 /* we access mParent & children() */
2270 AutoReadLock treeLock (this->treeLock());
2271
2272 Assert (children().size() == 0);
2273
2274 /* special treatment of the last hard disk in the chain: */
2275
2276 if (mParent.isNull())
2277 {
2278 rc = UnlockWrite (NULL);
2279 AssertComRC (rc);
2280 return rc;
2281 }
2282
2283 /* delete the differencing hard disk w/o children */
2284
2285 Assert (m.state == MediaState_Deleting);
2286
2287 /* go back to Created since deleteStorage() expects this state */
2288 m.state = MediaState_Created;
2289
2290 hdFrom = this;
2291
2292 rc = deleteStorageAndWait (&aProgress);
2293 }
2294 else
2295 {
2296 hdFrom = aChain->source();
2297
2298 rc = hdFrom->mergeToAndWait (aChain, &aProgress);
2299 }
2300 }
2301
2302 if (SUCCEEDED(rc))
2303 {
2304 /* mergeToAndWait() cannot uninitialize the initiator because of
2305 * possible AutoCallers on the current thread, deleteStorageAndWait()
2306 * doesn't do it either; do it ourselves */
2307 hdFrom->uninit();
2308 }
2309
2310 return rc;
2311}
2312
2313/**
2314 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
2315 * or fails. Frees memory occupied by @a aChain.
2316 *
2317 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
2318 * no real merge takes place).
2319 *
2320 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
2321 * reading.
2322 */
2323void HardDisk::cancelDiscard (MergeChain *aChain)
2324{
2325 AutoCaller autoCaller(this);
2326 AssertComRCReturnVoid (autoCaller.rc());
2327
2328 if (aChain == NULL)
2329 {
2330 AutoWriteLock alock(this);
2331
2332 /* we access mParent & children() */
2333 AutoReadLock treeLock (this->treeLock());
2334
2335 Assert (children().size() == 0);
2336
2337 /* special treatment of the last hard disk in the chain: */
2338
2339 if (mParent.isNull())
2340 {
2341 HRESULT rc = UnlockWrite (NULL);
2342 AssertComRC (rc);
2343 return;
2344 }
2345
2346 /* the differencing hard disk w/o children will be deleted, protect it
2347 * from attaching to other VMs (this is why Deleting) */
2348
2349 Assert (m.state == MediaState_Deleting);
2350 m.state = MediaState_Created;
2351
2352 return;
2353 }
2354
2355 /* delegate the rest to the profi */
2356 cancelMergeTo (aChain);
2357}
2358
2359/**
2360 * Returns a preferred format for differencing hard disks.
2361 */
2362Bstr HardDisk::preferredDiffFormat()
2363{
2364 Bstr format;
2365
2366 AutoCaller autoCaller(this);
2367 AssertComRCReturn (autoCaller.rc(), format);
2368
2369 /* mm.format is const, no need to lock */
2370 format = mm.format;
2371
2372 /* check that our own format supports diffs */
2373 if (!(mm.formatObj->capabilities() & HardDiskFormatCapabilities_Differencing))
2374 {
2375 /* use the default format if not */
2376 AutoReadLock propsLock (mVirtualBox->systemProperties());
2377 format = mVirtualBox->systemProperties()->defaultHardDiskFormat();
2378 }
2379
2380 return format;
2381}
2382
2383// protected methods
2384////////////////////////////////////////////////////////////////////////////////
2385
2386/**
2387 * Deletes the hard disk storage unit.
2388 *
2389 * If @a aProgress is not NULL but the object it points to is @c null then a new
2390 * progress object will be created and assigned to @a *aProgress on success,
2391 * otherwise the existing progress object is used. If Progress is NULL, then no
2392 * progress object is created/used at all.
2393 *
2394 * When @a aWait is @c false, this method will create a thread to perform the
2395 * delete operation asynchronously and will return immediately. Otherwise, it
2396 * will perform the operation on the calling thread and will not return to the
2397 * caller until the operation is completed. Note that @a aProgress cannot be
2398 * NULL when @a aWait is @c false (this method will assert in this case).
2399 *
2400 * @param aProgress Where to find/store a Progress object to track operation
2401 * completion.
2402 * @param aWait @c true if this method should block instead of creating
2403 * an asynchronous thread.
2404 *
2405 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
2406 * writing.
2407 */
2408HRESULT HardDisk::deleteStorage (ComObjPtr<Progress> *aProgress, bool aWait)
2409{
2410 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
2411
2412 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
2413 * ourselves atomically after detecting that deletion is possible to make
2414 * sure that we don't do that after another thread has done
2415 * VirtualBox::findHardDisk() but before it starts using us (provided that
2416 * it holds a mVirtualBox lock too of course). */
2417
2418 AutoWriteLock vboxLock (mVirtualBox);
2419
2420 AutoWriteLock alock(this);
2421
2422 if (!(mm.formatObj->capabilities() &
2423 (HardDiskFormatCapabilities_CreateDynamic |
2424 HardDiskFormatCapabilities_CreateFixed)))
2425 return setError (VBOX_E_NOT_SUPPORTED,
2426 tr ("Hard disk format '%ls' does not support storage deletion"),
2427 mm.format.raw());
2428
2429 /* Note that we are fine with Inaccessible state too: a) for symmetry with
2430 * create calls and b) because it doesn't really harm to try, if it is
2431 * really inaccessibke, the delete operation will fail anyway. Accepting
2432 * Inaccessible state is especially important because all registered hard
2433 * disks are initially Inaccessible upon VBoxSVC startup until
2434 * COMGETTER(State) is called. */
2435
2436 switch (m.state)
2437 {
2438 case MediaState_Created:
2439 case MediaState_Inaccessible:
2440 break;
2441 default:
2442 return setStateError();
2443 }
2444
2445 if (m.backRefs.size() != 0)
2446 return setError (VBOX_E_OBJECT_IN_USE,
2447 tr ("Hard disk '%ls' is attached to %d virtual machines"),
2448 m.locationFull.raw(), m.backRefs.size());
2449
2450 HRESULT rc = canClose();
2451 CheckComRCReturnRC(rc);
2452
2453 /* go to Deleting state before leaving the lock */
2454 m.state = MediaState_Deleting;
2455
2456 /* we need to leave this object's write lock now because of
2457 * unregisterWithVirtualBox() that locks treeLock() for writing */
2458 alock.leave();
2459
2460 /* try to remove from the list of known hard disks before performing actual
2461 * deletion (we favor the consistency of the media registry in the first
2462 * place which would have been broken if unregisterWithVirtualBox() failed
2463 * after we successfully deleted the storage) */
2464
2465 rc = unregisterWithVirtualBox();
2466
2467 alock.enter();
2468
2469 /* restore the state because we may fail below; we will set it later again*/
2470 m.state = MediaState_Created;
2471
2472 CheckComRCReturnRC(rc);
2473
2474 ComObjPtr<Progress> progress;
2475
2476 if (aProgress != NULL)
2477 {
2478 /* use the existing progress object... */
2479 progress = *aProgress;
2480
2481 /* ...but create a new one if it is null */
2482 if (progress.isNull())
2483 {
2484 progress.createObject();
2485 rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
2486 BstrFmt (tr ("Deleting hard disk storage unit '%ls'"),
2487 m.locationFull.raw()),
2488 FALSE /* aCancelable */);
2489 CheckComRCReturnRC(rc);
2490 }
2491 }
2492
2493 std::auto_ptr <Task> task (new Task (this, progress, Task::Delete));
2494 AssertComRCReturnRC(task->autoCaller.rc());
2495
2496 if (aWait)
2497 {
2498 /* go to Deleting state before starting the task */
2499 m.state = MediaState_Deleting;
2500
2501 rc = task->runNow();
2502 }
2503 else
2504 {
2505 rc = task->startThread();
2506 CheckComRCReturnRC(rc);
2507
2508 /* go to Deleting state before leaving the lock */
2509 m.state = MediaState_Deleting;
2510 }
2511
2512 /* task is now owned (or already deleted) by taskThread() so release it */
2513 task.release();
2514
2515 if (aProgress != NULL)
2516 {
2517 /* return progress to the caller */
2518 *aProgress = progress;
2519 }
2520
2521 return rc;
2522}
2523
2524/**
2525 * Creates a new differencing storage unit using the given target hard disk's
2526 * format and the location. Note that @c aTarget must be NotCreated.
2527 *
2528 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
2529 * this hard disk for reading assuming that the caller has already done so. This
2530 * is used when taking an online snaopshot (where all original hard disks are
2531 * locked for writing and must remain such). Note however that if @a aWait is
2532 * @c false and this method returns a success then the thread started by
2533 * this method will unlock the hard disk (unless it is in
2534 * MediaState_LockedWrite state) so make sure the hard disk is either in
2535 * MediaState_LockedWrite or call #LockRead() before calling this method! If @a
2536 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
2537 * make sure you do it yourself as needed.
2538 *
2539 * If @a aProgress is not NULL but the object it points to is @c null then a new
2540 * progress object will be created and assigned to @a *aProgress on success,
2541 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
2542 * progress object is created/used at all.
2543 *
2544 * When @a aWait is @c false, this method will create a thread to perform the
2545 * create operation asynchronously and will return immediately. Otherwise, it
2546 * will perform the operation on the calling thread and will not return to the
2547 * caller until the operation is completed. Note that @a aProgress cannot be
2548 * NULL when @a aWait is @c false (this method will assert in this case).
2549 *
2550 * @param aTarget Target hard disk.
2551 * @param aVariant Precise image variant to create.
2552 * @param aProgress Where to find/store a Progress object to track operation
2553 * completion.
2554 * @param aWait @c true if this method should block instead of creating
2555 * an asynchronous thread.
2556 *
2557 * @note Locks this object and @a aTarget for writing.
2558 */
2559HRESULT HardDisk::createDiffStorage(ComObjPtr<HardDisk> &aTarget,
2560 HardDiskVariant_T aVariant,
2561 ComObjPtr<Progress> *aProgress,
2562 bool aWait)
2563{
2564 AssertReturn(!aTarget.isNull(), E_FAIL);
2565 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
2566
2567 AutoCaller autoCaller(this);
2568 CheckComRCReturnRC(autoCaller.rc());
2569
2570 AutoCaller targetCaller (aTarget);
2571 CheckComRCReturnRC(targetCaller.rc());
2572
2573 AutoMultiWriteLock2 alock (this, aTarget);
2574
2575 AssertReturn(mm.type != HardDiskType_Writethrough, E_FAIL);
2576
2577 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
2578 AssertReturn(m.state == MediaState_LockedRead ||
2579 m.state == MediaState_LockedWrite, E_FAIL);
2580
2581 if (aTarget->m.state != MediaState_NotCreated)
2582 return aTarget->setStateError();
2583
2584 HRESULT rc = S_OK;
2585
2586 /* check that the hard disk is not attached to any VM in the current state*/
2587 for (BackRefList::const_iterator it = m.backRefs.begin();
2588 it != m.backRefs.end(); ++ it)
2589 {
2590 if (it->inCurState)
2591 {
2592 /* Note: when a VM snapshot is being taken, all normal hard disks
2593 * attached to the VM in the current state will be, as an exception,
2594 * also associated with the snapshot which is about to create (see
2595 * SnapshotMachine::init()) before deassociating them from the
2596 * current state (which takes place only on success in
2597 * Machine::fixupHardDisks()), so that the size of snapshotIds
2598 * will be 1 in this case. The given condition is used to filter out
2599 * this legal situatinon and do not report an error. */
2600
2601 if (it->snapshotIds.size() == 0)
2602 {
2603 return setError (VBOX_E_INVALID_OBJECT_STATE,
2604 tr ("Hard disk '%ls' is attached to a virtual machine with UUID {%RTuuid}. No differencing hard disks based on it may be created until it is detached"),
2605 m.locationFull.raw(), it->machineId.raw());
2606 }
2607
2608 Assert (it->snapshotIds.size() == 1);
2609 }
2610 }
2611
2612 ComObjPtr<Progress> progress;
2613
2614 if (aProgress != NULL)
2615 {
2616 /* use the existing progress object... */
2617 progress = *aProgress;
2618
2619 /* ...but create a new one if it is null */
2620 if (progress.isNull())
2621 {
2622 progress.createObject();
2623 rc = progress->init (mVirtualBox, static_cast<IHardDisk*> (this),
2624 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
2625 aTarget->m.locationFull.raw()),
2626 TRUE /* aCancelable */);
2627 CheckComRCReturnRC(rc);
2628 }
2629 }
2630
2631 /* setup task object and thread to carry out the operation
2632 * asynchronously */
2633
2634 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
2635 AssertComRCReturnRC(task->autoCaller.rc());
2636
2637 task->setData (aTarget);
2638 task->d.variant = aVariant;
2639
2640 /* register a task (it will deregister itself when done) */
2641 ++ mm.numCreateDiffTasks;
2642 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
2643
2644 if (aWait)
2645 {
2646 /* go to Creating state before starting the task */
2647 aTarget->m.state = MediaState_Creating;
2648
2649 rc = task->runNow();
2650 }
2651 else
2652 {
2653 rc = task->startThread();
2654 CheckComRCReturnRC(rc);
2655
2656 /* go to Creating state before leaving the lock */
2657 aTarget->m.state = MediaState_Creating;
2658 }
2659
2660 /* task is now owned (or already deleted) by taskThread() so release it */
2661 task.release();
2662
2663 if (aProgress != NULL)
2664 {
2665 /* return progress to the caller */
2666 *aProgress = progress;
2667 }
2668
2669 return rc;
2670}
2671
2672/**
2673 * Prepares this (source) hard disk, target hard disk and all intermediate hard
2674 * disks for the merge operation.
2675 *
2676 * This method is to be called prior to calling the #mergeTo() to perform
2677 * necessary consistency checks and place involved hard disks to appropriate
2678 * states. If #mergeTo() is not called or fails, the state modifications
2679 * performed by this method must be undone by #cancelMergeTo().
2680 *
2681 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
2682 * responsibility to detach the source and all intermediate hard disks before
2683 * calling #mergeTo() (which will fail otherwise).
2684 *
2685 * See #mergeTo() for more information about merging.
2686 *
2687 * @param aTarget Target hard disk.
2688 * @param aChain Where to store the created merge chain.
2689 * @param aIgnoreAttachments Don't check if the source or any intermediate
2690 * hard disk is attached to any VM.
2691 *
2692 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2693 * intermediate hard disks for writing.
2694 */
2695HRESULT HardDisk::prepareMergeTo(HardDisk *aTarget,
2696 MergeChain * &aChain,
2697 bool aIgnoreAttachments /*= false*/)
2698{
2699 AssertReturn(aTarget != NULL, E_FAIL);
2700
2701 AutoCaller autoCaller(this);
2702 AssertComRCReturnRC(autoCaller.rc());
2703
2704 AutoCaller targetCaller (aTarget);
2705 AssertComRCReturnRC(targetCaller.rc());
2706
2707 aChain = NULL;
2708
2709 /* we walk the tree */
2710 AutoReadLock treeLock (this->treeLock());
2711
2712 HRESULT rc = S_OK;
2713
2714 /* detect the merge direction */
2715 bool forward;
2716 {
2717 HardDisk *parent = mParent;
2718 while (parent != NULL && parent != aTarget)
2719 parent = parent->mParent;
2720 if (parent == aTarget)
2721 forward = false;
2722 else
2723 {
2724 parent = aTarget->mParent;
2725 while (parent != NULL && parent != this)
2726 parent = parent->mParent;
2727 if (parent == this)
2728 forward = true;
2729 else
2730 {
2731 Bstr tgtLoc;
2732 {
2733 AutoReadLock alock(this);
2734 tgtLoc = aTarget->locationFull();
2735 }
2736
2737 AutoReadLock alock(this);
2738 return setError (E_FAIL,
2739 tr ("Hard disks '%ls' and '%ls' are unrelated"),
2740 m.locationFull.raw(), tgtLoc.raw());
2741 }
2742 }
2743 }
2744
2745 /* build the chain (will do necessary checks and state changes) */
2746 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2747 aIgnoreAttachments));
2748 {
2749 HardDisk *last = forward ? aTarget : this;
2750 HardDisk *first = forward ? this : aTarget;
2751
2752 for (;;)
2753 {
2754 if (last == aTarget)
2755 rc = chain->addTarget (last);
2756 else if (last == this)
2757 rc = chain->addSource (last);
2758 else
2759 rc = chain->addIntermediate (last);
2760 CheckComRCReturnRC(rc);
2761
2762 if (last == first)
2763 break;
2764
2765 last = last->mParent;
2766 }
2767 }
2768
2769 aChain = chain.release();
2770
2771 return S_OK;
2772}
2773
2774/**
2775 * Merges this hard disk to the specified hard disk which must be either its
2776 * direct ancestor or descendant.
2777 *
2778 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2779 * get two varians of the merge operation:
2780 *
2781 * forward merge
2782 * ------------------------->
2783 * [Extra] <- SOURCE <- Intermediate <- TARGET
2784 * Any Del Del LockWr
2785 *
2786 *
2787 * backward merge
2788 * <-------------------------
2789 * TARGET <- Intermediate <- SOURCE <- [Extra]
2790 * LockWr Del Del LockWr
2791 *
2792 * Each scheme shows the involved hard disks on the hard disk chain where
2793 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2794 * the hard disk must have at a time of the mergeTo() call.
2795 *
2796 * The hard disks in the square braces may be absent (e.g. when the forward
2797 * operation takes place and SOURCE is the base hard disk, or when the backward
2798 * merge operation takes place and TARGET is the last child in the chain) but if
2799 * they present they are involved too as shown.
2800 *
2801 * Nor the source hard disk neither intermediate hard disks may be attached to
2802 * any VM directly or in the snapshot, otherwise this method will assert.
2803 *
2804 * The #prepareMergeTo() method must be called prior to this method to place all
2805 * involved to necessary states and perform other consistency checks.
2806 *
2807 * If @a aWait is @c true then this method will perform the operation on the
2808 * calling thread and will not return to the caller until the operation is
2809 * completed. When this method succeeds, all intermediate hard disk objects in
2810 * the chain will be uninitialized, the state of the target hard disk (and all
2811 * involved extra hard disks) will be restored and @a aChain will be deleted.
2812 * Note that this (source) hard disk is not uninitialized because of possible
2813 * AutoCaller instances held by the caller of this method on the current thread.
2814 * It's therefore the responsibility of the caller to call HardDisk::uninit()
2815 * after releasing all callers in this case!
2816 *
2817 * If @a aWait is @c false then this method will crea,te a thread to perform the
2818 * create operation asynchronously and will return immediately. If the operation
2819 * succeeds, the thread will uninitialize the source hard disk object and all
2820 * intermediate hard disk objects in the chain, reset the state of the target
2821 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2822 * operation fails, the thread will only reset the states of all involved hard
2823 * disks and delete @a aChain.
2824 *
2825 * When this method fails (regardless of the @a aWait mode), it is a caller's
2826 * responsiblity to undo state changes and delete @a aChain using
2827 * #cancelMergeTo().
2828 *
2829 * If @a aProgress is not NULL but the object it points to is @c null then a new
2830 * progress object will be created and assigned to @a *aProgress on success,
2831 * otherwise the existing progress object is used. If Progress is NULL, then no
2832 * progress object is created/used at all. Note that @a aProgress cannot be
2833 * NULL when @a aWait is @c false (this method will assert in this case).
2834 *
2835 * @param aChain Merge chain created by #prepareMergeTo().
2836 * @param aProgress Where to find/store a Progress object to track operation
2837 * completion.
2838 * @param aWait @c true if this method should block instead of creating
2839 * an asynchronous thread.
2840 *
2841 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2842 * for writing.
2843 */
2844HRESULT HardDisk::mergeTo(MergeChain *aChain,
2845 ComObjPtr<Progress> *aProgress,
2846 bool aWait)
2847{
2848 AssertReturn(aChain != NULL, E_FAIL);
2849 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
2850
2851 AutoCaller autoCaller(this);
2852 CheckComRCReturnRC(autoCaller.rc());
2853
2854 HRESULT rc = S_OK;
2855
2856 ComObjPtr<Progress> progress;
2857
2858 if (aProgress != NULL)
2859 {
2860 /* use the existing progress object... */
2861 progress = *aProgress;
2862
2863 /* ...but create a new one if it is null */
2864 if (progress.isNull())
2865 {
2866 AutoReadLock alock(this);
2867
2868 progress.createObject();
2869 rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
2870 BstrFmt (tr ("Merging hard disk '%s' to '%s'"),
2871 name().raw(), aChain->target()->name().raw()),
2872 TRUE /* aCancelable */);
2873 CheckComRCReturnRC(rc);
2874 }
2875 }
2876
2877 /* setup task object and thread to carry out the operation
2878 * asynchronously */
2879
2880 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2881 AssertComRCReturnRC(task->autoCaller.rc());
2882
2883 task->setData (aChain);
2884
2885 /* Note: task owns aChain (will delete it when not needed) in all cases
2886 * except when @a aWait is @c true and runNow() fails -- in this case
2887 * aChain will be left away because cancelMergeTo() will be applied by the
2888 * caller on it as it is required in the documentation above */
2889
2890 if (aWait)
2891 {
2892 rc = task->runNow();
2893 }
2894 else
2895 {
2896 rc = task->startThread();
2897 CheckComRCReturnRC(rc);
2898 }
2899
2900 /* task is now owned (or already deleted) by taskThread() so release it */
2901 task.release();
2902
2903 if (aProgress != NULL)
2904 {
2905 /* return progress to the caller */
2906 *aProgress = progress;
2907 }
2908
2909 return rc;
2910}
2911
2912/**
2913 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2914 * or fails. Frees memory occupied by @a aChain.
2915 *
2916 * @param aChain Merge chain created by #prepareMergeTo().
2917 *
2918 * @note Locks the hard disks from the chain for writing.
2919 */
2920void HardDisk::cancelMergeTo (MergeChain *aChain)
2921{
2922 AutoCaller autoCaller(this);
2923 AssertComRCReturnVoid (autoCaller.rc());
2924
2925 AssertReturnVoid (aChain != NULL);
2926
2927 /* the destructor will do the thing */
2928 delete aChain;
2929}
2930
2931// private methods
2932////////////////////////////////////////////////////////////////////////////////
2933
2934/**
2935 * Sets the value of m.location and calculates the value of m.locationFull.
2936 *
2937 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2938 * locations and to prepend the default hard disk folder if the given location
2939 * string does not contain any path information at all.
2940 *
2941 * Also, if the specified location is a file path that ends with '/' then the
2942 * file name part will be generated by this method automatically in the format
2943 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2944 * and assign to this medium, and <ext> is the default extension for this
2945 * medium's storage format. Note that this procedure requires the media state to
2946 * be NotCreated and will return a faiulre otherwise.
2947 *
2948 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2949 * then it can be relative to the VirtualBox home directory.
2950 *
2951 * @note Must be called from under this object's write lock.
2952 */
2953HRESULT HardDisk::setLocation (CBSTR aLocation)
2954{
2955 /// @todo so far, we assert but later it makes sense to support null
2956 /// locations for hard disks that are not yet created fail to create a
2957 /// storage unit instead
2958 CheckComArgStrNotEmptyOrNull (aLocation);
2959
2960 AutoCaller autoCaller(this);
2961 AssertComRCReturnRC(autoCaller.rc());
2962
2963 /* formatObj may be null only when initializing from an existing path and
2964 * no format is known yet */
2965 AssertReturn((!mm.format.isNull() && !mm.formatObj.isNull()) ||
2966 (autoCaller.state() == InInit &&
2967 m.state != MediaState_NotCreated && m.id.isEmpty() &&
2968 mm.format.isNull() && mm.formatObj.isNull()),
2969 E_FAIL);
2970
2971 /* are we dealing with a new hard disk constructed using the existing
2972 * location? */
2973 bool isImport = mm.format.isNull();
2974
2975 if (isImport ||
2976 (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File))
2977 {
2978 Guid id;
2979
2980 Utf8Str location (aLocation);
2981
2982 if (m.state == MediaState_NotCreated)
2983 {
2984 /* must be a file (formatObj must be already known) */
2985 Assert (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File);
2986
2987 if (RTPathFilename (location) == NULL)
2988 {
2989 /* no file name is given (either an empty string or ends with a
2990 * slash), generate a new UUID + file name if the state allows
2991 * this */
2992
2993 ComAssertMsgRet (!mm.formatObj->fileExtensions().empty(),
2994 ("Must be at least one extension if it is HardDiskFormatCapabilities_File\n"),
2995 E_FAIL);
2996
2997 Bstr ext = mm.formatObj->fileExtensions().front();
2998 ComAssertMsgRet (!ext.isEmpty(),
2999 ("Default extension must not be empty\n"),
3000 E_FAIL);
3001
3002 id.create();
3003
3004 location = Utf8StrFmt ("%s{%RTuuid}.%ls",
3005 location.raw(), id.raw(), ext.raw());
3006 }
3007 }
3008
3009 /* append the default folder if no path is given */
3010 if (!RTPathHavePath (location))
3011 {
3012 AutoReadLock propsLock (mVirtualBox->systemProperties());
3013 location = Utf8StrFmt ("%ls%c%s",
3014 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
3015 RTPATH_DELIMITER,
3016 location.raw());
3017 }
3018
3019 /* get the full file name */
3020 Utf8Str locationFull;
3021 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
3022 if (RT_FAILURE(vrc))
3023 return setError (VBOX_E_FILE_ERROR,
3024 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
3025 location.raw(), vrc);
3026
3027 /* detect the backend from the storage unit if importing */
3028 if (isImport)
3029 {
3030 char *backendName = NULL;
3031
3032 /* is it a file? */
3033 {
3034 RTFILE file;
3035 vrc = RTFileOpen (&file, locationFull, RTFILE_O_READ);
3036 if (RT_SUCCESS(vrc))
3037 RTFileClose (file);
3038 }
3039 if (RT_SUCCESS(vrc))
3040 {
3041 vrc = VDGetFormat (locationFull, &backendName);
3042 }
3043 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3044 {
3045 /* assume it's not a file, restore the original location */
3046 location = locationFull = aLocation;
3047 vrc = VDGetFormat (locationFull, &backendName);
3048 }
3049
3050 if (RT_FAILURE(vrc))
3051 {
3052 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3053 return setError (VBOX_E_FILE_ERROR,
3054 tr ("Could not find file for the hard disk '%s' (%Rrc)"),
3055 locationFull.raw(), vrc);
3056 else
3057 return setError (VBOX_E_IPRT_ERROR,
3058 tr ("Could not get the storage format of the hard disk '%s' (%Rrc)"),
3059 locationFull.raw(), vrc);
3060 }
3061
3062 ComAssertRet (backendName != NULL && *backendName != '\0', E_FAIL);
3063
3064 HRESULT rc = setFormat (Bstr (backendName));
3065 RTStrFree (backendName);
3066
3067 /* setFormat() must not fail since we've just used the backend so
3068 * the format object must be there */
3069 AssertComRCReturnRC(rc);
3070 }
3071
3072 /* is it still a file? */
3073 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
3074 {
3075 m.location = location;
3076 m.locationFull = locationFull;
3077
3078 if (m.state == MediaState_NotCreated)
3079 {
3080 /* assign a new UUID (this UUID will be used when calling
3081 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3082 * also do that if we didn't generate it to make sure it is
3083 * either generated by us or reset to null */
3084 unconst(m.id) = id;
3085 }
3086 }
3087 else
3088 {
3089 m.location = locationFull;
3090 m.locationFull = locationFull;
3091 }
3092 }
3093 else
3094 {
3095 m.location = aLocation;
3096 m.locationFull = aLocation;
3097 }
3098
3099 return S_OK;
3100}
3101
3102/**
3103 * Checks that the format ID is valid and sets it on success.
3104 *
3105 * Note that this method will caller-reference the format object on success!
3106 * This reference must be released somewhere to let the HardDiskFormat object be
3107 * uninitialized.
3108 *
3109 * @note Must be called from under this object's write lock.
3110 */
3111HRESULT HardDisk::setFormat (CBSTR aFormat)
3112{
3113 /* get the format object first */
3114 {
3115 AutoReadLock propsLock (mVirtualBox->systemProperties());
3116
3117 unconst(mm.formatObj)
3118 = mVirtualBox->systemProperties()->hardDiskFormat (aFormat);
3119 if (mm.formatObj.isNull())
3120 return setError (E_INVALIDARG,
3121 tr ("Invalid hard disk storage format '%ls'"), aFormat);
3122
3123 /* reference the format permanently to prevent its unexpected
3124 * uninitialization */
3125 HRESULT rc = mm.formatObj->addCaller();
3126 AssertComRCReturnRC(rc);
3127
3128 /* get properties (preinsert them as keys in the map). Note that the
3129 * map doesn't grow over the object life time since the set of
3130 * properties is meant to be constant. */
3131
3132 Assert (mm.properties.empty());
3133
3134 for (HardDiskFormat::PropertyList::const_iterator it =
3135 mm.formatObj->properties().begin();
3136 it != mm.formatObj->properties().end();
3137 ++ it)
3138 {
3139 mm.properties.insert (std::make_pair (it->name, Bstr::Null));
3140 }
3141 }
3142
3143 unconst(mm.format) = aFormat;
3144
3145 return S_OK;
3146}
3147
3148/**
3149 * Queries information from the image file.
3150 *
3151 * As a result of this call, the accessibility state and data members such as
3152 * size and description will be updated with the current information.
3153 *
3154 * Reimplements MediumBase::queryInfo() to query hard disk information using the
3155 * VD backend interface.
3156 *
3157 * @note This method may block during a system I/O call that checks storage
3158 * accessibility.
3159 *
3160 * @note Locks treeLock() for reading and writing (for new diff media checked
3161 * for the first time). Locks mParent for reading. Locks this object for
3162 * writing.
3163 */
3164HRESULT HardDisk::queryInfo()
3165{
3166 AutoWriteLock alock(this);
3167
3168 AssertReturn(m.state == MediaState_Created ||
3169 m.state == MediaState_Inaccessible ||
3170 m.state == MediaState_LockedRead ||
3171 m.state == MediaState_LockedWrite,
3172 E_FAIL);
3173
3174 HRESULT rc = S_OK;
3175
3176 int vrc = VINF_SUCCESS;
3177
3178 /* check if a blocking queryInfo() call is in progress on some other thread,
3179 * and wait for it to finish if so instead of querying data ourselves */
3180 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
3181 {
3182 Assert (m.state == MediaState_LockedRead);
3183
3184 ++ m.queryInfoCallers;
3185 alock.leave();
3186
3187 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
3188
3189 alock.enter();
3190 -- m.queryInfoCallers;
3191
3192 if (m.queryInfoCallers == 0)
3193 {
3194 /* last waiting caller deletes the semaphore */
3195 RTSemEventMultiDestroy (m.queryInfoSem);
3196 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
3197 }
3198
3199 AssertRC (vrc);
3200
3201 return S_OK;
3202 }
3203
3204 /* lazily create a semaphore for possible callers */
3205 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
3206 ComAssertRCRet (vrc, E_FAIL);
3207
3208 bool tempStateSet = false;
3209 if (m.state != MediaState_LockedRead &&
3210 m.state != MediaState_LockedWrite)
3211 {
3212 /* Cause other methods to prevent any modifications before leaving the
3213 * lock. Note that clients will never see this temporary state change
3214 * since any COMGETTER(State) is (or will be) blocked until we finish
3215 * and restore the actual state. */
3216 m.state = MediaState_LockedRead;
3217 tempStateSet = true;
3218 }
3219
3220 /* leave the lock before a blocking operation */
3221 alock.leave();
3222
3223 bool success = false;
3224 Utf8Str lastAccessError;
3225
3226 try
3227 {
3228 Utf8Str location (m.locationFull);
3229
3230 /* are we dealing with a new hard disk constructed using the existing
3231 * location? */
3232 bool isImport = m.id.isEmpty();
3233
3234 PVBOXHDD hdd;
3235 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
3236 ComAssertRCThrow (vrc, E_FAIL);
3237
3238 try
3239 {
3240 unsigned flags = VD_OPEN_FLAGS_INFO;
3241
3242 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3243 * hard disks because that would prevent necessary modifications
3244 * when opening hard disks of some third-party formats for the first
3245 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3246 * generate an UUID if it is missing) */
3247 if ( (mm.hddOpenMode == OpenReadOnly)
3248 || !isImport
3249 )
3250 flags |= VD_OPEN_FLAGS_READONLY;
3251
3252 /** @todo This kind of opening of images is assuming that diff
3253 * images can be opened as base images. Should be fixed ASAP. */
3254 vrc = VDOpen(hdd,
3255 Utf8Str(mm.format),
3256 location,
3257 flags,
3258 mm.vdDiskIfaces);
3259 if (RT_FAILURE(vrc))
3260 {
3261 lastAccessError = Utf8StrFmt (
3262 tr ("Could not open the hard disk '%ls'%s"),
3263 m.locationFull.raw(), vdError (vrc).raw());
3264 throw S_OK;
3265 }
3266
3267 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_Uuid)
3268 {
3269 /* modify the UUIDs if necessary */
3270 if (mm.setImageId)
3271 {
3272 vrc = VDSetUuid(hdd, 0, mm.imageId);
3273 ComAssertRCThrow(vrc, E_FAIL);
3274 }
3275 if (mm.setParentId)
3276 {
3277 vrc = VDSetParentUuid(hdd, 0, mm.parentId);
3278 ComAssertRCThrow(vrc, E_FAIL);
3279 }
3280 /* zap the information, these are no long-term members */
3281 mm.setImageId = false;
3282 unconst(mm.imageId).clear();
3283 mm.setParentId = false;
3284 unconst(mm.parentId).clear();
3285
3286 /* check the UUID */
3287 RTUUID uuid;
3288 vrc = VDGetUuid(hdd, 0, &uuid);
3289 ComAssertRCThrow(vrc, E_FAIL);
3290
3291 if (isImport)
3292 {
3293 unconst(m.id) = uuid;
3294
3295 if (m.id.isEmpty() && (mm.hddOpenMode == OpenReadOnly))
3296 // only when importing a VDMK that has no UUID, create one in memory
3297 unconst(m.id).create();
3298 }
3299 else
3300 {
3301 Assert (!m.id.isEmpty());
3302
3303 if (m.id != uuid)
3304 {
3305 lastAccessError = Utf8StrFmt (
3306 tr ("UUID {%RTuuid} of the hard disk '%ls' does not match the value {%RTuuid} stored in the media registry ('%ls')"),
3307 &uuid, m.locationFull.raw(), m.id.raw(),
3308 mVirtualBox->settingsFileName().raw());
3309 throw S_OK;
3310 }
3311 }
3312 }
3313 else
3314 {
3315 /* the backend does not support storing UUIDs within the
3316 * underlying storage so use what we store in XML */
3317
3318 /* generate an UUID for an imported UUID-less hard disk */
3319 if (isImport)
3320 {
3321 if (mm.setImageId)
3322 unconst(m.id) = mm.imageId;
3323 else
3324 unconst(m.id).create();
3325 }
3326 }
3327
3328 /* check the type */
3329 unsigned uImageFlags;
3330 vrc = VDGetImageFlags (hdd, 0, &uImageFlags);
3331 ComAssertRCThrow (vrc, E_FAIL);
3332
3333 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3334 {
3335 RTUUID parentId;
3336 vrc = VDGetParentUuid (hdd, 0, &parentId);
3337 ComAssertRCThrow (vrc, E_FAIL);
3338
3339 if (isImport)
3340 {
3341 /* the parent must be known to us. Note that we freely
3342 * call locking methods of mVirtualBox and parent from the
3343 * write lock (breaking the {parent,child} lock order)
3344 * because there may be no concurrent access to the just
3345 * opened hard disk on ther threads yet (and init() will
3346 * fail if this method reporst MediaState_Inaccessible) */
3347
3348 Guid id = parentId;
3349 ComObjPtr<HardDisk> parent;
3350 rc = mVirtualBox->findHardDisk(&id, NULL,
3351 false /* aSetError */,
3352 &parent);
3353 if (FAILED (rc))
3354 {
3355 lastAccessError = Utf8StrFmt (
3356 tr ("Parent hard disk with UUID {%RTuuid} of the hard disk '%ls' is not found in the media registry ('%ls')"),
3357 &parentId, m.locationFull.raw(),
3358 mVirtualBox->settingsFileName().raw());
3359 throw S_OK;
3360 }
3361
3362 /* deassociate from VirtualBox, associate with parent */
3363
3364 mVirtualBox->removeDependentChild (this);
3365
3366 /* we set mParent & children() */
3367 AutoWriteLock treeLock (this->treeLock());
3368
3369 Assert (mParent.isNull());
3370 mParent = parent;
3371 mParent->addDependentChild (this);
3372 }
3373 else
3374 {
3375 /* we access mParent */
3376 AutoReadLock treeLock (this->treeLock());
3377
3378 /* check that parent UUIDs match. Note that there's no need
3379 * for the parent's AutoCaller (our lifetime is bound to
3380 * it) */
3381
3382 if (mParent.isNull())
3383 {
3384 lastAccessError = Utf8StrFmt (
3385 tr ("Hard disk '%ls' is differencing but it is not associated with any parent hard disk in the media registry ('%ls')"),
3386 m.locationFull.raw(),
3387 mVirtualBox->settingsFileName().raw());
3388 throw S_OK;
3389 }
3390
3391 AutoReadLock parentLock (mParent);
3392 if (mParent->state() != MediaState_Inaccessible &&
3393 mParent->id() != parentId)
3394 {
3395 lastAccessError = Utf8StrFmt (
3396 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' does not match UUID {%RTuuid} of its parent hard disk stored in the media registry ('%ls')"),
3397 &parentId, m.locationFull.raw(),
3398 mParent->id().raw(),
3399 mVirtualBox->settingsFileName().raw());
3400 throw S_OK;
3401 }
3402
3403 /// @todo NEWMEDIA what to do if the parent is not
3404 /// accessible while the diff is? Probably, nothing. The
3405 /// real code will detect the mismatch anyway.
3406 }
3407 }
3408
3409 m.size = VDGetFileSize (hdd, 0);
3410 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
3411
3412 success = true;
3413 }
3414 catch (HRESULT aRC)
3415 {
3416 rc = aRC;
3417 }
3418
3419 VDDestroy (hdd);
3420
3421 }
3422 catch (HRESULT aRC)
3423 {
3424 rc = aRC;
3425 }
3426
3427 alock.enter();
3428
3429 if (success)
3430 m.lastAccessError.setNull();
3431 else
3432 {
3433 m.lastAccessError = lastAccessError;
3434 LogWarningFunc (("'%ls' is not accessible (error='%ls', rc=%Rhrc, vrc=%Rrc)\n",
3435 m.locationFull.raw(), m.lastAccessError.raw(),
3436 rc, vrc));
3437 }
3438
3439 /* inform other callers if there are any */
3440 if (m.queryInfoCallers > 0)
3441 {
3442 RTSemEventMultiSignal (m.queryInfoSem);
3443 }
3444 else
3445 {
3446 /* delete the semaphore ourselves */
3447 RTSemEventMultiDestroy (m.queryInfoSem);
3448 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
3449 }
3450
3451 if (tempStateSet)
3452 {
3453 /* Set the proper state according to the result of the check */
3454 if (success)
3455 m.state = MediaState_Created;
3456 else
3457 m.state = MediaState_Inaccessible;
3458 }
3459 else
3460 {
3461 /* we're locked, use a special field to store the result */
3462 m.accessibleInLock = success;
3463 }
3464
3465 return rc;
3466}
3467
3468/**
3469 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
3470 * write lock.
3471 *
3472 * @note Also reused by HardDisk::Reset().
3473 *
3474 * @note Locks treeLock() for reading.
3475 */
3476HRESULT HardDisk::canClose()
3477{
3478 /* we access children */
3479 AutoReadLock treeLock (this->treeLock());
3480
3481 if (children().size() != 0)
3482 return setError (E_FAIL,
3483 tr ("Hard disk '%ls' has %d child hard disks"),
3484 children().size());
3485
3486 return S_OK;
3487}
3488
3489/**
3490 * @note Called from within this object's AutoWriteLock.
3491 */
3492HRESULT HardDisk::canAttach(const Guid & /* aMachineId */,
3493 const Guid & /* aSnapshotId */)
3494{
3495 if (mm.numCreateDiffTasks > 0)
3496 return setError (E_FAIL,
3497 tr ("One or more differencing child hard disks are being created for the hard disk '%ls' (%u)"),
3498 m.locationFull.raw(), mm.numCreateDiffTasks);
3499
3500 return S_OK;
3501}
3502
3503/**
3504 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
3505 * from under mVirtualBox write lock.
3506 *
3507 * @note Locks treeLock() for writing.
3508 */
3509HRESULT HardDisk::unregisterWithVirtualBox()
3510{
3511 /* Note that we need to de-associate ourselves from the parent to let
3512 * unregisterHardDisk() properly save the registry */
3513
3514 /* we modify mParent and access children */
3515 AutoWriteLock treeLock (this->treeLock());
3516
3517 const ComObjPtr<HardDisk, ComWeakRef> parent = mParent;
3518
3519 AssertReturn(children().size() == 0, E_FAIL);
3520
3521 if (!mParent.isNull())
3522 {
3523 /* deassociate from the parent, associate with VirtualBox */
3524 mVirtualBox->addDependentChild (this);
3525 mParent->removeDependentChild (this);
3526 mParent.setNull();
3527 }
3528
3529 HRESULT rc = mVirtualBox->unregisterHardDisk(this);
3530
3531 if (FAILED (rc))
3532 {
3533 if (!parent.isNull())
3534 {
3535 /* re-associate with the parent as we are still relatives in the
3536 * registry */
3537 mParent = parent;
3538 mParent->addDependentChild (this);
3539 mVirtualBox->removeDependentChild (this);
3540 }
3541 }
3542
3543 return rc;
3544}
3545
3546/**
3547 * Returns the last error message collected by the vdErrorCall callback and
3548 * resets it.
3549 *
3550 * The error message is returned prepended with a dot and a space, like this:
3551 * <code>
3552 * ". <error_text> (%Rrc)"
3553 * </code>
3554 * to make it easily appendable to a more general error message. The @c %Rrc
3555 * format string is given @a aVRC as an argument.
3556 *
3557 * If there is no last error message collected by vdErrorCall or if it is a
3558 * null or empty string, then this function returns the following text:
3559 * <code>
3560 * " (%Rrc)"
3561 * </code>
3562 *
3563 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3564 * the callback isn't called by more than one thread at a time.
3565 *
3566 * @param aVRC VBox error code to use when no error message is provided.
3567 */
3568Utf8Str HardDisk::vdError (int aVRC)
3569{
3570 Utf8Str error;
3571
3572 if (mm.vdError.isEmpty())
3573 error = Utf8StrFmt (" (%Rrc)", aVRC);
3574 else
3575 error = Utf8StrFmt (".\n%s", mm.vdError.raw());
3576
3577 mm.vdError.setNull();
3578
3579 return error;
3580}
3581
3582/**
3583 * Error message callback.
3584 *
3585 * Puts the reported error message to the mm.vdError field.
3586 *
3587 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3588 * the callback isn't called by more than one thread at a time.
3589 *
3590 * @param pvUser The opaque data passed on container creation.
3591 * @param rc The VBox error code.
3592 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
3593 * @param pszFormat Error message format string.
3594 * @param va Error message arguments.
3595 */
3596/*static*/
3597DECLCALLBACK(void) HardDisk::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
3598 const char *pszFormat, va_list va)
3599{
3600 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
3601
3602 HardDisk *that = static_cast<HardDisk*>(pvUser);
3603 AssertReturnVoid (that != NULL);
3604
3605 if (that->mm.vdError.isEmpty())
3606 that->mm.vdError =
3607 Utf8StrFmt ("%s (%Rrc)", Utf8StrFmtVA (pszFormat, va).raw(), rc);
3608 else
3609 that->mm.vdError =
3610 Utf8StrFmt ("%s.\n%s (%Rrc)", that->mm.vdError.raw(),
3611 Utf8StrFmtVA (pszFormat, va).raw(), rc);
3612}
3613
3614/**
3615 * PFNVMPROGRESS callback handler for Task operations.
3616 *
3617 * @param uPercent Completetion precentage (0-100).
3618 * @param pvUser Pointer to the Progress instance.
3619 */
3620/*static*/
3621DECLCALLBACK(int) HardDisk::vdProgressCall(PVM /* pVM */, unsigned uPercent,
3622 void *pvUser)
3623{
3624 HardDisk *that = static_cast<HardDisk*>(pvUser);
3625 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
3626
3627 if (that->mm.vdProgress != NULL)
3628 {
3629 /* update the progress object, capping it at 99% as the final percent
3630 * is used for additional operations like setting the UUIDs and similar. */
3631 HRESULT rc = that->mm.vdProgress->setCurrentOperationProgress(uPercent * 99 / 100);
3632 if (FAILED(rc))
3633 {
3634 if (rc == E_FAIL)
3635 return VERR_CANCELLED;
3636 else
3637 return VERR_INVALID_STATE;
3638 }
3639 }
3640
3641 return VINF_SUCCESS;
3642}
3643
3644/* static */
3645DECLCALLBACK(bool) HardDisk::vdConfigAreKeysValid (void *pvUser,
3646 const char * /* pszzValid */)
3647{
3648 HardDisk *that = static_cast<HardDisk*>(pvUser);
3649 AssertReturn(that != NULL, false);
3650
3651 /* we always return true since the only keys we have are those found in
3652 * VDBACKENDINFO */
3653 return true;
3654}
3655
3656/* static */
3657DECLCALLBACK(int) HardDisk::vdConfigQuerySize(void *pvUser, const char *pszName,
3658 size_t *pcbValue)
3659{
3660 AssertReturn(VALID_PTR (pcbValue), VERR_INVALID_POINTER);
3661
3662 HardDisk *that = static_cast<HardDisk*>(pvUser);
3663 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
3664
3665 Data::PropertyMap::const_iterator it =
3666 that->mm.properties.find (Bstr (pszName));
3667 if (it == that->mm.properties.end())
3668 return VERR_CFGM_VALUE_NOT_FOUND;
3669
3670 /* we interpret null values as "no value" in HardDisk */
3671 if (it->second.isNull())
3672 return VERR_CFGM_VALUE_NOT_FOUND;
3673
3674 *pcbValue = it->second.length() + 1 /* include terminator */;
3675
3676 return VINF_SUCCESS;
3677}
3678
3679/* static */
3680DECLCALLBACK(int) HardDisk::vdConfigQuery (void *pvUser, const char *pszName,
3681 char *pszValue, size_t cchValue)
3682{
3683 AssertReturn(VALID_PTR (pszValue), VERR_INVALID_POINTER);
3684
3685 HardDisk *that = static_cast<HardDisk*>(pvUser);
3686 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
3687
3688 Data::PropertyMap::const_iterator it =
3689 that->mm.properties.find (Bstr (pszName));
3690 if (it == that->mm.properties.end())
3691 return VERR_CFGM_VALUE_NOT_FOUND;
3692
3693 Utf8Str value = it->second;
3694 if (value.length() >= cchValue)
3695 return VERR_CFGM_NOT_ENOUGH_SPACE;
3696
3697 /* we interpret null values as "no value" in HardDisk */
3698 if (it->second.isNull())
3699 return VERR_CFGM_VALUE_NOT_FOUND;
3700
3701 memcpy (pszValue, value, value.length() + 1);
3702
3703 return VINF_SUCCESS;
3704}
3705
3706/**
3707 * Thread function for time-consuming tasks.
3708 *
3709 * The Task structure passed to @a pvUser must be allocated using new and will
3710 * be freed by this method before it returns.
3711 *
3712 * @param pvUser Pointer to the Task instance.
3713 */
3714/* static */
3715DECLCALLBACK(int) HardDisk::taskThread (RTTHREAD thread, void *pvUser)
3716{
3717 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
3718 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
3719
3720 bool isAsync = thread != NIL_RTTHREAD;
3721
3722 HardDisk *that = task->that;
3723
3724 /// @todo ugly hack, fix ComAssert... later
3725 #define setError that->setError
3726
3727 /* Note: no need in AutoCaller because Task does that */
3728
3729 LogFlowFuncEnter();
3730 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
3731
3732 HRESULT rc = S_OK;
3733
3734 switch (task->operation)
3735 {
3736 ////////////////////////////////////////////////////////////////////////
3737
3738 case Task::CreateBase:
3739 {
3740 /* The lock is also used as a signal from the task initiator (which
3741 * releases it only after RTThreadCreate()) that we can start the job */
3742 AutoWriteLock thatLock (that);
3743
3744 /* these parameters we need after creation */
3745 uint64_t size = 0, logicalSize = 0;
3746
3747 /* The object may request a specific UUID (through a special form of
3748 * the setLocation() argument). Otherwise we have to generate it */
3749 Guid id = that->m.id;
3750 bool generateUuid = id.isEmpty();
3751 if (generateUuid)
3752 {
3753 id.create();
3754 /* VirtualBox::registerHardDisk() will need UUID */
3755 unconst(that->m.id) = id;
3756 }
3757
3758 try
3759 {
3760 PVBOXHDD hdd;
3761 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3762 ComAssertRCThrow (vrc, E_FAIL);
3763
3764 Utf8Str format (that->mm.format);
3765 Utf8Str location (that->m.locationFull);
3766 /* uint64_t capabilities = */ that->mm.formatObj->capabilities();
3767
3768 /* unlock before the potentially lengthy operation */
3769 Assert (that->m.state == MediaState_Creating);
3770 thatLock.leave();
3771
3772 try
3773 {
3774 /* ensure the directory exists */
3775 rc = VirtualBox::ensureFilePathExists (location);
3776 CheckComRCThrowRC (rc);
3777
3778 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
3779
3780 /* needed for vdProgressCallback */
3781 that->mm.vdProgress = task->progress;
3782
3783 vrc = VDCreateBase (hdd, format, location,
3784 task->d.size * _1M,
3785 task->d.variant,
3786 NULL, &geo, &geo, id.raw(),
3787 VD_OPEN_FLAGS_NORMAL,
3788 NULL, that->mm.vdDiskIfaces);
3789
3790 if (RT_FAILURE(vrc))
3791 {
3792 throw setError (E_FAIL,
3793 tr ("Could not create the hard disk storage unit '%s'%s"),
3794 location.raw(), that->vdError (vrc).raw());
3795 }
3796
3797 size = VDGetFileSize (hdd, 0);
3798 logicalSize = VDGetSize (hdd, 0) / _1M;
3799 }
3800 catch (HRESULT aRC) { rc = aRC; }
3801
3802 VDDestroy (hdd);
3803 }
3804 catch (HRESULT aRC) { rc = aRC; }
3805
3806 if (SUCCEEDED(rc))
3807 {
3808 /* register with mVirtualBox as the last step and move to
3809 * Created state only on success (leaving an orphan file is
3810 * better than breaking media registry consistency) */
3811 rc = that->mVirtualBox->registerHardDisk(that);
3812 }
3813
3814 thatLock.maybeEnter();
3815
3816 if (SUCCEEDED(rc))
3817 {
3818 that->m.state = MediaState_Created;
3819
3820 that->m.size = size;
3821 that->mm.logicalSize = logicalSize;
3822 }
3823 else
3824 {
3825 /* back to NotCreated on failure */
3826 that->m.state = MediaState_NotCreated;
3827
3828 /* reset UUID to prevent it from being reused next time */
3829 if (generateUuid)
3830 unconst(that->m.id).clear();
3831 }
3832
3833 break;
3834 }
3835
3836 ////////////////////////////////////////////////////////////////////////
3837
3838 case Task::CreateDiff:
3839 {
3840 ComObjPtr<HardDisk> &target = task->d.target;
3841
3842 /* Lock both in {parent,child} order. The lock is also used as a
3843 * signal from the task initiator (which releases it only after
3844 * RTThreadCreate()) that we can start the job*/
3845 AutoMultiWriteLock2 thatLock (that, target);
3846
3847 uint64_t size = 0, logicalSize = 0;
3848
3849 /* The object may request a specific UUID (through a special form of
3850 * the setLocation() argument). Otherwise we have to generate it */
3851 Guid targetId = target->m.id;
3852 bool generateUuid = targetId.isEmpty();
3853 if (generateUuid)
3854 {
3855 targetId.create();
3856 /* VirtualBox::registerHardDisk() will need UUID */
3857 unconst(target->m.id) = targetId;
3858 }
3859
3860 try
3861 {
3862 PVBOXHDD hdd;
3863 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3864 ComAssertRCThrow (vrc, E_FAIL);
3865
3866 Guid id = that->m.id;
3867 Utf8Str format (that->mm.format);
3868 Utf8Str location (that->m.locationFull);
3869
3870 Utf8Str targetFormat (target->mm.format);
3871 Utf8Str targetLocation (target->m.locationFull);
3872
3873 Assert (target->m.state == MediaState_Creating);
3874
3875 /* Note: MediaState_LockedWrite is ok when taking an online
3876 * snapshot */
3877 Assert (that->m.state == MediaState_LockedRead ||
3878 that->m.state == MediaState_LockedWrite);
3879
3880 /* unlock before the potentially lengthy operation */
3881 thatLock.leave();
3882
3883 try
3884 {
3885 vrc = VDOpen (hdd, format, location,
3886 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3887 that->mm.vdDiskIfaces);
3888 if (RT_FAILURE(vrc))
3889 {
3890 throw setError (E_FAIL,
3891 tr ("Could not open the hard disk storage unit '%s'%s"),
3892 location.raw(), that->vdError (vrc).raw());
3893 }
3894
3895 /* ensure the target directory exists */
3896 rc = VirtualBox::ensureFilePathExists (targetLocation);
3897 CheckComRCThrowRC (rc);
3898
3899 /* needed for vdProgressCallback */
3900 that->mm.vdProgress = task->progress;
3901
3902 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
3903 * be on the safe side. */
3904 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
3905 task->d.variant,
3906 NULL, targetId.raw(),
3907 id.raw(),
3908 VD_OPEN_FLAGS_NORMAL,
3909 target->mm.vdDiskIfaces,
3910 that->mm.vdDiskIfaces);
3911
3912 that->mm.vdProgress = NULL;
3913
3914 if (RT_FAILURE(vrc))
3915 {
3916 throw setError (E_FAIL,
3917 tr ("Could not create the differencing hard disk storage unit '%s'%s"),
3918 targetLocation.raw(), that->vdError (vrc).raw());
3919 }
3920
3921 size = VDGetFileSize (hdd, 1);
3922 logicalSize = VDGetSize (hdd, 1) / _1M;
3923 }
3924 catch (HRESULT aRC) { rc = aRC; }
3925
3926 VDDestroy (hdd);
3927 }
3928 catch (HRESULT aRC) { rc = aRC; }
3929
3930 if (SUCCEEDED(rc))
3931 {
3932 /* we set mParent & children() (note that thatLock is released
3933 * here), but lock VirtualBox first to follow the rule */
3934 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3935 that->treeLock());
3936
3937 Assert (target->mParent.isNull());
3938
3939 /* associate the child with the parent and deassociate from
3940 * VirtualBox */
3941 target->mParent = that;
3942 that->addDependentChild (target);
3943 target->mVirtualBox->removeDependentChild (target);
3944
3945 /* diffs for immutable hard disks are auto-reset by default */
3946 target->mm.autoReset =
3947 that->root()->mm.type == HardDiskType_Immutable ?
3948 TRUE : FALSE;
3949
3950 /* register with mVirtualBox as the last step and move to
3951 * Created state only on success (leaving an orphan file is
3952 * better than breaking media registry consistency) */
3953 rc = that->mVirtualBox->registerHardDisk (target);
3954
3955 if (FAILED (rc))
3956 {
3957 /* break the parent association on failure to register */
3958 target->mVirtualBox->addDependentChild (target);
3959 that->removeDependentChild (target);
3960 target->mParent.setNull();
3961 }
3962 }
3963
3964 thatLock.maybeEnter();
3965
3966 if (SUCCEEDED(rc))
3967 {
3968 target->m.state = MediaState_Created;
3969
3970 target->m.size = size;
3971 target->mm.logicalSize = logicalSize;
3972 }
3973 else
3974 {
3975 /* back to NotCreated on failure */
3976 target->m.state = MediaState_NotCreated;
3977
3978 target->mm.autoReset = FALSE;
3979
3980 /* reset UUID to prevent it from being reused next time */
3981 if (generateUuid)
3982 unconst(target->m.id).clear();
3983 }
3984
3985 if (isAsync)
3986 {
3987 /* unlock ourselves when done (unless in MediaState_LockedWrite
3988 * state because of taking the online snapshot*/
3989 if (that->m.state != MediaState_LockedWrite)
3990 {
3991 HRESULT rc2 = that->UnlockRead (NULL);
3992 AssertComRC (rc2);
3993 }
3994 }
3995
3996 /* deregister the task registered in createDiffStorage() */
3997 Assert (that->mm.numCreateDiffTasks != 0);
3998 -- that->mm.numCreateDiffTasks;
3999
4000 /* Note that in sync mode, it's the caller's responsibility to
4001 * unlock the hard disk */
4002
4003 break;
4004 }
4005
4006 ////////////////////////////////////////////////////////////////////////
4007
4008 case Task::Merge:
4009 {
4010 /* The lock is also used as a signal from the task initiator (which
4011 * releases it only after RTThreadCreate()) that we can start the
4012 * job. We don't actually need the lock for anything else since the
4013 * object is protected by MediaState_Deleting and we don't modify
4014 * its sensitive fields below */
4015 {
4016 AutoWriteLock thatLock (that);
4017 }
4018
4019 MergeChain *chain = task->d.chain.get();
4020
4021#if 0
4022 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
4023#endif
4024
4025 try
4026 {
4027 PVBOXHDD hdd;
4028 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4029 ComAssertRCThrow (vrc, E_FAIL);
4030
4031 try
4032 {
4033 /* Open all hard disks in the chain (they are in the
4034 * {parent,child} order in there. Note that we don't lock
4035 * objects in this chain since they must be in states
4036 * (Deleting and LockedWrite) that prevent from changing
4037 * their format and location fields from outside. */
4038
4039 for (MergeChain::const_iterator it = chain->begin();
4040 it != chain->end(); ++ it)
4041 {
4042 /* complex sanity (sane complexity) */
4043 Assert ((chain->isForward() &&
4044 ((*it != chain->back() &&
4045 (*it)->m.state == MediaState_Deleting) ||
4046 (*it == chain->back() &&
4047 (*it)->m.state == MediaState_LockedWrite))) ||
4048 (!chain->isForward() &&
4049 ((*it != chain->front() &&
4050 (*it)->m.state == MediaState_Deleting) ||
4051 (*it == chain->front() &&
4052 (*it)->m.state == MediaState_LockedWrite))));
4053
4054 Assert (*it == chain->target() ||
4055 (*it)->m.backRefs.size() == 0);
4056
4057 /* open the first image with VDOPEN_FLAGS_INFO because
4058 * it's not necessarily the base one */
4059 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
4060 Utf8Str ((*it)->m.locationFull),
4061 it == chain->begin() ?
4062 VD_OPEN_FLAGS_INFO : 0,
4063 (*it)->mm.vdDiskIfaces);
4064 if (RT_FAILURE(vrc))
4065 throw vrc;
4066#if 0
4067 LogFlow (("*** MERGE disk = %ls\n",
4068 (*it)->m.locationFull.raw()));
4069#endif
4070 }
4071
4072 /* needed for vdProgressCallback */
4073 that->mm.vdProgress = task->progress;
4074
4075 unsigned start = chain->isForward() ?
4076 0 : (unsigned)chain->size() - 1;
4077 unsigned end = chain->isForward() ?
4078 (unsigned)chain->size() - 1 : 0;
4079#if 0
4080 LogFlow (("*** MERGE from %d to %d\n", start, end));
4081#endif
4082 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
4083
4084 that->mm.vdProgress = NULL;
4085
4086 if (RT_FAILURE(vrc))
4087 throw vrc;
4088
4089 /* update parent UUIDs */
4090 /// @todo VDMerge should be taught to do so, including the
4091 /// multiple children case
4092 if (chain->isForward())
4093 {
4094 /* target's UUID needs to be updated (note that target
4095 * is the only image in the container on success) */
4096 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
4097 if (RT_FAILURE(vrc))
4098 throw vrc;
4099 }
4100 else
4101 {
4102 /* we need to update UUIDs of all source's children
4103 * which cannot be part of the container at once so
4104 * add each one in there individually */
4105 if (chain->children().size() > 0)
4106 {
4107 for (List::const_iterator it = chain->children().begin();
4108 it != chain->children().end(); ++ it)
4109 {
4110 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
4111 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
4112 Utf8Str ((*it)->m.locationFull),
4113 VD_OPEN_FLAGS_INFO,
4114 (*it)->mm.vdDiskIfaces);
4115 if (RT_FAILURE(vrc))
4116 throw vrc;
4117
4118 vrc = VDSetParentUuid (hdd, 1,
4119 chain->target()->m.id);
4120 if (RT_FAILURE(vrc))
4121 throw vrc;
4122
4123 vrc = VDClose (hdd, false /* fDelete */);
4124 if (RT_FAILURE(vrc))
4125 throw vrc;
4126 }
4127 }
4128 }
4129 }
4130 catch (HRESULT aRC) { rc = aRC; }
4131 catch (int aVRC)
4132 {
4133 throw setError (E_FAIL,
4134 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
4135 chain->source()->m.locationFull.raw(),
4136 chain->target()->m.locationFull.raw(),
4137 that->vdError (aVRC).raw());
4138 }
4139
4140 VDDestroy (hdd);
4141 }
4142 catch (HRESULT aRC) { rc = aRC; }
4143
4144 HRESULT rc2;
4145
4146 bool saveSettingsFailed = false;
4147
4148 if (SUCCEEDED(rc))
4149 {
4150 /* all hard disks but the target were successfully deleted by
4151 * VDMerge; reparent the last one and uninitialize deleted */
4152
4153 /* we set mParent & children() (note that thatLock is released
4154 * here), but lock VirtualBox first to follow the rule */
4155 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
4156 that->treeLock());
4157
4158 HardDisk *source = chain->source();
4159 HardDisk *target = chain->target();
4160
4161 if (chain->isForward())
4162 {
4163 /* first, unregister the target since it may become a base
4164 * hard disk which needs re-registration */
4165 rc2 = target->mVirtualBox->
4166 unregisterHardDisk (target, false /* aSaveSettings */);
4167 AssertComRC (rc2);
4168
4169 /* then, reparent it and disconnect the deleted branch at
4170 * both ends (chain->parent() is source's parent) */
4171 target->mParent->removeDependentChild (target);
4172 target->mParent = chain->parent();
4173 if (!target->mParent.isNull())
4174 {
4175 target->mParent->addDependentChild (target);
4176 target->mParent->removeDependentChild (source);
4177 source->mParent.setNull();
4178 }
4179 else
4180 {
4181 target->mVirtualBox->addDependentChild (target);
4182 target->mVirtualBox->removeDependentChild (source);
4183 }
4184
4185 /* then, register again */
4186 rc2 = target->mVirtualBox->
4187 registerHardDisk (target, false /* aSaveSettings */);
4188 AssertComRC (rc2);
4189 }
4190 else
4191 {
4192 Assert (target->children().size() == 1);
4193 HardDisk *targetChild = target->children().front();
4194
4195 /* disconnect the deleted branch at the elder end */
4196 target->removeDependentChild (targetChild);
4197 targetChild->mParent.setNull();
4198
4199 const List &children = chain->children();
4200
4201 /* reparent source's chidren and disconnect the deleted
4202 * branch at the younger end m*/
4203 if (children.size() > 0)
4204 {
4205 /* obey {parent,child} lock order */
4206 AutoWriteLock sourceLock (source);
4207
4208 for (List::const_iterator it = children.begin();
4209 it != children.end(); ++ it)
4210 {
4211 AutoWriteLock childLock (*it);
4212
4213 (*it)->mParent = target;
4214 (*it)->mParent->addDependentChild (*it);
4215 source->removeDependentChild (*it);
4216 }
4217 }
4218 }
4219
4220 /* try to save the hard disk registry */
4221 rc = that->mVirtualBox->saveSettings();
4222
4223 if (SUCCEEDED(rc))
4224 {
4225 /* unregister and uninitialize all hard disks in the chain
4226 * but the target */
4227
4228 for (MergeChain::iterator it = chain->begin();
4229 it != chain->end();)
4230 {
4231 if (*it == chain->target())
4232 {
4233 ++ it;
4234 continue;
4235 }
4236
4237 rc2 = (*it)->mVirtualBox->
4238 unregisterHardDisk(*it, false /* aSaveSettings */);
4239 AssertComRC (rc2);
4240
4241 /* now, uninitialize the deleted hard disk (note that
4242 * due to the Deleting state, uninit() will not touch
4243 * the parent-child relationship so we need to
4244 * uninitialize each disk individually) */
4245
4246 /* note that the operation initiator hard disk (which is
4247 * normally also the source hard disk) is a special case
4248 * -- there is one more caller added by Task to it which
4249 * we must release. Also, if we are in sync mode, the
4250 * caller may still hold an AutoCaller instance for it
4251 * and therefore we cannot uninit() it (it's therefore
4252 * the caller's responsibility) */
4253 if (*it == that)
4254 task->autoCaller.release();
4255
4256 /* release the caller added by MergeChain before
4257 * uninit() */
4258 (*it)->releaseCaller();
4259
4260 if (isAsync || *it != that)
4261 (*it)->uninit();
4262
4263 /* delete (to prevent uninitialization in MergeChain
4264 * dtor) and advance to the next item */
4265 it = chain->erase (it);
4266 }
4267
4268 /* Note that states of all other hard disks (target, parent,
4269 * children) will be restored by the MergeChain dtor */
4270 }
4271 else
4272 {
4273 /* too bad if we fail, but we'll need to rollback everything
4274 * we did above to at least keep the HD tree in sync with
4275 * the current registry on disk */
4276
4277 saveSettingsFailed = true;
4278
4279 /// @todo NEWMEDIA implement a proper undo
4280
4281 AssertFailed();
4282 }
4283 }
4284
4285 if (FAILED (rc))
4286 {
4287 /* Here we come if either VDMerge() failed (in which case we
4288 * assume that it tried to do everything to make a further
4289 * retry possible -- e.g. not deleted intermediate hard disks
4290 * and so on) or VirtualBox::saveSettings() failed (where we
4291 * should have the original tree but with intermediate storage
4292 * units deleted by VDMerge()). We have to only restore states
4293 * (through the MergeChain dtor) unless we are run synchronously
4294 * in which case it's the responsibility of the caller as stated
4295 * in the mergeTo() docs. The latter also implies that we
4296 * don't own the merge chain, so release it in this case. */
4297
4298 if (!isAsync)
4299 task->d.chain.release();
4300
4301 NOREF (saveSettingsFailed);
4302 }
4303
4304 break;
4305 }
4306
4307 ////////////////////////////////////////////////////////////////////////
4308
4309 case Task::Clone:
4310 {
4311 ComObjPtr<HardDisk> &target = task->d.target;
4312 ComObjPtr<HardDisk> &parent = task->d.parentDisk;
4313
4314 /* Lock all in {parent,child} order. The lock is also used as a
4315 * signal from the task initiator (which releases it only after
4316 * RTThreadCreate()) that we can start the job. */
4317 AutoMultiWriteLock3 thatLock (that, target, parent);
4318
4319 ImageChain *srcChain = task->d.source.get();
4320 ImageChain *parentChain = task->d.parent.get();
4321
4322 uint64_t size = 0, logicalSize = 0;
4323
4324 /* The object may request a specific UUID (through a special form of
4325 * the setLocation() argument). Otherwise we have to generate it */
4326 Guid targetId = target->m.id;
4327 bool generateUuid = targetId.isEmpty();
4328 if (generateUuid)
4329 {
4330 targetId.create();
4331 /* VirtualBox::registerHardDisk() will need UUID */
4332 unconst(target->m.id) = targetId;
4333 }
4334
4335 try
4336 {
4337 PVBOXHDD hdd;
4338 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4339 ComAssertRCThrow (vrc, E_FAIL);
4340
4341 try
4342 {
4343 /* Open all hard disk images in the source chain. */
4344 for (List::const_iterator it = srcChain->begin();
4345 it != srcChain->end(); ++ it)
4346 {
4347 /* sanity check */
4348 Assert ((*it)->m.state == MediaState_LockedRead);
4349
4350 /** Open all images in read-only mode. */
4351 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
4352 Utf8Str ((*it)->m.locationFull),
4353 VD_OPEN_FLAGS_READONLY,
4354 (*it)->mm.vdDiskIfaces);
4355 if (RT_FAILURE(vrc))
4356 {
4357 throw setError (E_FAIL,
4358 tr ("Could not open the hard disk storage unit '%s'%s"),
4359 Utf8Str ((*it)->m.locationFull).raw(),
4360 that->vdError (vrc).raw());
4361 }
4362 }
4363
4364 /* unlock before the potentially lengthy operation */
4365 thatLock.leave();
4366
4367 Utf8Str targetFormat (target->mm.format);
4368 Utf8Str targetLocation (target->m.locationFull);
4369
4370 Assert ( target->m.state == MediaState_Creating
4371 || target->m.state == MediaState_LockedWrite);
4372 Assert (that->m.state == MediaState_LockedRead);
4373 Assert (parent.isNull() || parent->m.state == MediaState_LockedRead);
4374
4375 /* ensure the target directory exists */
4376 rc = VirtualBox::ensureFilePathExists (targetLocation);
4377 CheckComRCThrowRC (rc);
4378
4379 /* needed for vdProgressCallback */
4380 that->mm.vdProgress = task->progress;
4381
4382 PVBOXHDD targetHdd;
4383 int vrc = VDCreate (that->mm.vdDiskIfaces, &targetHdd);
4384 ComAssertRCThrow (vrc, E_FAIL);
4385
4386 try
4387 {
4388 /* Open all hard disk images in the parent chain. */
4389 for (List::const_iterator it = parentChain->begin();
4390 it != parentChain->end(); ++ it)
4391 {
4392 /* sanity check */
4393 Assert ( (*it)->m.state == MediaState_LockedRead
4394 || (*it)->m.state == MediaState_LockedWrite);
4395
4396 /* Open all images in appropriate mode. */
4397 vrc = VDOpen (targetHdd, Utf8Str ((*it)->mm.format),
4398 Utf8Str ((*it)->m.locationFull),
4399 ((*it)->m.state == MediaState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
4400 (*it)->mm.vdDiskIfaces);
4401 if (RT_FAILURE(vrc))
4402 {
4403 throw setError (E_FAIL,
4404 tr ("Could not open the hard disk storage unit '%s'%s"),
4405 Utf8Str ((*it)->m.locationFull).raw(),
4406 that->vdError (vrc).raw());
4407 }
4408 }
4409
4410 vrc = VDCopy (hdd, VD_LAST_IMAGE, targetHdd,
4411 targetFormat,
4412 target->m.state == MediaState_Creating ? targetLocation.raw() : (char *)NULL,
4413 false, 0,
4414 task->d.variant, targetId.raw(), NULL,
4415 target->mm.vdDiskIfaces,
4416 that->mm.vdDiskIfaces);
4417
4418 that->mm.vdProgress = NULL;
4419
4420 if (RT_FAILURE(vrc))
4421 {
4422 throw setError (E_FAIL,
4423 tr ("Could not create the clone hard disk '%s'%s"),
4424 targetLocation.raw(), that->vdError (vrc).raw());
4425 }
4426 size = VDGetFileSize (targetHdd, 0);
4427 logicalSize = VDGetSize (targetHdd, 0) / _1M;
4428 }
4429 catch (HRESULT aRC) { rc = aRC; }
4430
4431 VDDestroy (targetHdd);
4432 }
4433 catch (HRESULT aRC) { rc = aRC; }
4434
4435 VDDestroy (hdd);
4436 }
4437 catch (HRESULT aRC) { rc = aRC; }
4438
4439 /* Only do the parent changes for newly created images. */
4440 if (target->m.state == MediaState_Creating)
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
4480 thatLock.maybeEnter();
4481
4482 if (target->m.state == MediaState_Creating)
4483 {
4484 if (SUCCEEDED(rc))
4485 {
4486 target->m.state = MediaState_Created;
4487
4488 target->m.size = size;
4489 target->mm.logicalSize = logicalSize;
4490 }
4491 else
4492 {
4493 /* back to NotCreated on failure */
4494 target->m.state = MediaState_NotCreated;
4495
4496 /* reset UUID to prevent it from being reused next time */
4497 if (generateUuid)
4498 unconst(target->m.id).clear();
4499 }
4500 }
4501
4502 /* Everything is explicitly unlocked when the task exits,
4503 * as the task destruction also destroys the source chain. */
4504
4505 /* Make sure the source chain is released early. It could happen
4506 * that we get a deadlock in Appliance::Import when Medium::Close
4507 * is called & the source chain is released at the same time. */
4508 task->d.source.reset();
4509 break;
4510 }
4511
4512 ////////////////////////////////////////////////////////////////////////
4513
4514 case Task::Delete:
4515 {
4516 /* The lock is also used as a signal from the task initiator (which
4517 * releases it only after RTThreadCreate()) that we can start the job */
4518 AutoWriteLock thatLock (that);
4519
4520 try
4521 {
4522 PVBOXHDD hdd;
4523 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4524 ComAssertRCThrow (vrc, E_FAIL);
4525
4526 Utf8Str format (that->mm.format);
4527 Utf8Str location (that->m.locationFull);
4528
4529 /* unlock before the potentially lengthy operation */
4530 Assert (that->m.state == MediaState_Deleting);
4531 thatLock.leave();
4532
4533 try
4534 {
4535 vrc = VDOpen (hdd, format, location,
4536 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4537 that->mm.vdDiskIfaces);
4538 if (RT_SUCCESS(vrc))
4539 vrc = VDClose (hdd, true /* fDelete */);
4540
4541 if (RT_FAILURE(vrc))
4542 {
4543 throw setError (E_FAIL,
4544 tr ("Could not delete the hard disk storage unit '%s'%s"),
4545 location.raw(), that->vdError (vrc).raw());
4546 }
4547
4548 }
4549 catch (HRESULT aRC) { rc = aRC; }
4550
4551 VDDestroy (hdd);
4552 }
4553 catch (HRESULT aRC) { rc = aRC; }
4554
4555 thatLock.maybeEnter();
4556
4557 /* go to the NotCreated state even on failure since the storage
4558 * may have been already partially deleted and cannot be used any
4559 * more. One will be able to manually re-open the storage if really
4560 * needed to re-register it. */
4561 that->m.state = MediaState_NotCreated;
4562
4563 /* Reset UUID to prevent Create* from reusing it again */
4564 unconst(that->m.id).clear();
4565
4566 break;
4567 }
4568
4569 case Task::Reset:
4570 {
4571 /* The lock is also used as a signal from the task initiator (which
4572 * releases it only after RTThreadCreate()) that we can start the job */
4573 AutoWriteLock thatLock (that);
4574
4575 /// @todo Below we use a pair of delete/create operations to reset
4576 /// the diff contents but the most efficient way will of course be
4577 /// to add a VDResetDiff() API call
4578
4579 uint64_t size = 0, logicalSize = 0;
4580
4581 try
4582 {
4583 PVBOXHDD hdd;
4584 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4585 ComAssertRCThrow (vrc, E_FAIL);
4586
4587 Guid id = that->m.id;
4588 Utf8Str format (that->mm.format);
4589 Utf8Str location (that->m.locationFull);
4590
4591 Guid parentId = that->mParent->m.id;
4592 Utf8Str parentFormat (that->mParent->mm.format);
4593 Utf8Str parentLocation (that->mParent->m.locationFull);
4594
4595 Assert (that->m.state == MediaState_LockedWrite);
4596
4597 /* unlock before the potentially lengthy operation */
4598 thatLock.leave();
4599
4600 try
4601 {
4602 /* first, delete the storage unit */
4603 vrc = VDOpen (hdd, format, location,
4604 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4605 that->mm.vdDiskIfaces);
4606 if (RT_SUCCESS(vrc))
4607 vrc = VDClose (hdd, true /* fDelete */);
4608
4609 if (RT_FAILURE(vrc))
4610 {
4611 throw setError (E_FAIL,
4612 tr ("Could not delete the hard disk storage unit '%s'%s"),
4613 location.raw(), that->vdError (vrc).raw());
4614 }
4615
4616 /* next, create it again */
4617 vrc = VDOpen (hdd, parentFormat, parentLocation,
4618 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4619 that->mm.vdDiskIfaces);
4620 if (RT_FAILURE(vrc))
4621 {
4622 throw setError (E_FAIL,
4623 tr ("Could not open the hard disk storage unit '%s'%s"),
4624 parentLocation.raw(), that->vdError (vrc).raw());
4625 }
4626
4627 /* needed for vdProgressCallback */
4628 that->mm.vdProgress = task->progress;
4629
4630 vrc = VDCreateDiff (hdd, format, location,
4631 /// @todo use the same image variant as before
4632 VD_IMAGE_FLAGS_NONE,
4633 NULL, id.raw(),
4634 parentId.raw(),
4635 VD_OPEN_FLAGS_NORMAL,
4636 that->mm.vdDiskIfaces,
4637 that->mm.vdDiskIfaces);
4638
4639 that->mm.vdProgress = NULL;
4640
4641 if (RT_FAILURE(vrc))
4642 {
4643 throw setError (E_FAIL,
4644 tr ("Could not create the differencing hard disk 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 unit '%s'%s"),
4715 Utf8Str ((*it)->m.locationFull).raw(),
4716 that->vdError (vrc).raw());
4717 }
4718 }
4719
4720 /* unlock before the potentially lengthy operation */
4721 thatLock.leave();
4722
4723 Assert (that->m.state == MediaState_LockedWrite);
4724
4725 /* needed for vdProgressCallback */
4726 that->mm.vdProgress = task->progress;
4727
4728 vrc = VDCompact (hdd, VD_LAST_IMAGE, that->mm.vdDiskIfaces);
4729
4730 that->mm.vdProgress = NULL;
4731
4732 if (RT_FAILURE(vrc))
4733 {
4734 if (vrc == VERR_NOT_SUPPORTED)
4735 throw setError(VBOX_E_NOT_SUPPORTED,
4736 tr("Compacting is not supported yet for hard disk '%s'"),
4737 Utf8Str (that->m.locationFull).raw());
4738 else if (vrc == VERR_NOT_IMPLEMENTED)
4739 throw setError(E_NOTIMPL,
4740 tr("Compacting is not implemented, hard disk '%s'"),
4741 Utf8Str (that->m.locationFull).raw());
4742 else
4743 throw setError (E_FAIL,
4744 tr ("Could not compact hard disk '%s'%s"),
4745 Utf8Str (that->m.locationFull).raw(),
4746 that->vdError (vrc).raw());
4747 }
4748 }
4749 catch (HRESULT aRC) { rc = aRC; }
4750
4751 VDDestroy (hdd);
4752 }
4753 catch (HRESULT aRC) { rc = aRC; }
4754
4755 /* Everything is explicitly unlocked when the task exits,
4756 * as the task destruction also destroys the image chain. */
4757
4758 break;
4759 }
4760
4761 default:
4762 AssertFailedReturn (VERR_GENERAL_FAILURE);
4763 }
4764
4765 /* complete the progress if run asynchronously */
4766 if (isAsync)
4767 {
4768 if (!task->progress.isNull())
4769 task->progress->notifyComplete (rc);
4770 }
4771 else
4772 {
4773 task->rc = rc;
4774 }
4775
4776 LogFlowFunc (("rc=%Rhrc\n", rc));
4777 LogFlowFuncLeave();
4778
4779 return VINF_SUCCESS;
4780
4781 /// @todo ugly hack, fix ComAssert... later
4782 #undef setError
4783}
4784/* 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