VirtualBox

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

Last change on this file since 19075 was 19036, checked in by vboxsync, 16 years ago

Main/HardDisk: implement compact functionality

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