VirtualBox

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

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

Main: minor optimization

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

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