VirtualBox

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

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

Backing out r49763 to fix Windows burns.

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