VirtualBox

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

Last change on this file since 20969 was 20945, checked in by vboxsync, 16 years ago

API/HardDisk: allow cloning to existing images. just a stub for now.

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

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