VirtualBox

source: vbox/trunk/src/VBox/Main/HardDisk2Impl.cpp@ 14596

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

Main: Implemented IHardDisk2::getProperty()/setProperty().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 114.9 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2008 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22#include "HardDisk2Impl.h"
23
24#include "ProgressImpl.h"
25#include "SystemPropertiesImpl.h"
26
27#include "Logging.h"
28
29#include <VBox/com/array.h>
30#include <VBox/com/SupportErrorInfo.h>
31
32#include <VBox/err.h>
33
34#include <iprt/param.h>
35#include <iprt/path.h>
36#include <iprt/file.h>
37
38#include <list>
39#include <memory>
40
41////////////////////////////////////////////////////////////////////////////////
42// Globals
43////////////////////////////////////////////////////////////////////////////////
44
45/**
46 * Asynchronous task thread parameter bucket.
47 *
48 * Note that instances of this class must be created using new() because the
49 * task thread function will delete them when the task is complete!
50 *
51 * @note The constructor of this class adds a caller on the managed HardDisk2
52 * object which is automatically released upon destruction.
53 */
54struct HardDisk2::Task : public com::SupportErrorInfoBase
55{
56 enum Operation { CreateDynamic, CreateFixed, CreateDiff, Merge, Delete };
57
58 HardDisk2 *that;
59 VirtualBoxBaseProto::AutoCaller autoCaller;
60
61 ComObjPtr <Progress> progress;
62 Operation operation;
63
64 /** Where to save the result when executed using #runNow(). */
65 HRESULT rc;
66
67 Task (HardDisk2 *aThat, Progress *aProgress, Operation aOperation)
68 : that (aThat), autoCaller (aThat)
69 , progress (aProgress)
70 , operation (aOperation)
71 , rc (S_OK) {}
72
73 ~Task();
74
75 void setData (HardDisk2 *aTarget)
76 {
77 d.target = aTarget;
78 HRESULT rc = d.target->addCaller();
79 AssertComRC (rc);
80 }
81
82 void setData (MergeChain *aChain)
83 {
84 AssertReturnVoid (aChain != NULL);
85 d.chain.reset (aChain);
86 }
87
88 HRESULT startThread();
89 HRESULT runNow();
90
91 struct Data
92 {
93 Data() : size (0) {}
94
95 /* CreateDynamic, CreateStatic */
96
97 uint64_t size;
98
99 /* CreateDiff */
100
101 ComObjPtr <HardDisk2> target;
102
103 /* Merge */
104
105 /** Hard disks to merge, in {parent,child} order */
106 std::auto_ptr <MergeChain> chain;
107 }
108 d;
109
110protected:
111
112 // SupportErrorInfoBase interface
113 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk2); }
114 const char *componentName() const { return HardDisk2::ComponentName(); }
115};
116
117HardDisk2::Task::~Task()
118{
119 /* remove callers added by setData() */
120 if (!d.target.isNull())
121 d.target->releaseCaller();
122}
123
124/**
125 * Starts a new thread driven by the HardDisk2::taskThread() function and passes
126 * this Task instance as an argument.
127 *
128 * Note that if this method returns success, this Task object becomes an ownee
129 * of the started thread and will be automatically deleted when the thread
130 * terminates.
131 *
132 * @note When the task is executed by this method, IProgress::notifyComplete()
133 * is automatically called for the progress object associated with this
134 * task when the task is finished to signal the operation completion for
135 * other threads asynchronously waiting for it.
136 */
137HRESULT HardDisk2::Task::startThread()
138{
139 int vrc = RTThreadCreate (NULL, HardDisk2::taskThread, this,
140 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
141 "HardDisk::Task");
142 ComAssertMsgRCRet (vrc,
143 ("Could not create HardDisk::Task thread (%Rrc)\n", vrc), E_FAIL);
144
145 return S_OK;
146}
147
148/**
149 * Runs HardDisk2::taskThread() by passing it this Task instance as an argument
150 * on the current thread instead of creating a new one.
151 *
152 * This call implies that it is made on another temporary thread created for
153 * some asynchronous task. Avoid calling it from a normal thread since the task
154 * operatinos are potentially lengthy and will block the calling thread in this
155 * case.
156 *
157 * Note that this Task object will be deleted by taskThread() when this method
158 * returns!
159 *
160 * @note When the task is executed by this method, IProgress::notifyComplete()
161 * is not called for the progress object associated with this task when
162 * the task is finished. Instead, the result of the operation is returned
163 * by this method directly and it's the caller's responsibility to
164 * complete the progress object in this case.
165 */
166HRESULT HardDisk2::Task::runNow()
167{
168 HardDisk2::taskThread (NIL_RTTHREAD, this);
169
170 return rc;
171}
172
173////////////////////////////////////////////////////////////////////////////////
174
175/**
176 * Helper class for merge operations.
177 *
178 * @note It is assumed that when modifying methods of this class are called,
179 * HardDisk2::treeLock() is held in read mode.
180 */
181class HardDisk2::MergeChain : public HardDisk2::List,
182 public com::SupportErrorInfoBase
183{
184public:
185
186 MergeChain (bool aForward, bool aIgnoreAttachments)
187 : mForward (aForward)
188 , mIgnoreAttachments (aIgnoreAttachments) {}
189
190 ~MergeChain()
191 {
192 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
193 {
194 HRESULT rc = (*it)->UnlockWrite (NULL);
195 AssertComRC (rc);
196
197 (*it)->releaseCaller();
198 }
199
200 for (iterator it = begin(); it != end(); ++ it)
201 {
202 AutoWriteLock alock (*it);
203 Assert ((*it)->m.state == MediaState_LockedWrite ||
204 (*it)->m.state == MediaState_Deleting);
205 if ((*it)->m.state == MediaState_LockedWrite)
206 (*it)->UnlockWrite (NULL);
207 else
208 (*it)->m.state = MediaState_Created;
209
210 (*it)->releaseCaller();
211 }
212
213 if (!mParent.isNull())
214 mParent->releaseCaller();
215 }
216
217 HRESULT addSource (HardDisk2 *aHardDisk)
218 {
219 HRESULT rc = aHardDisk->addCaller();
220 CheckComRCReturnRC (rc);
221
222 AutoWriteLock alock (aHardDisk);
223
224 if (mForward)
225 {
226 rc = checkChildrenAndAttachmentsAndImmutable (aHardDisk);
227 if (FAILED (rc))
228 {
229 aHardDisk->releaseCaller();
230 return rc;
231 }
232 }
233
234 /* go to Deleting */
235 switch (aHardDisk->m.state)
236 {
237 case MediaState_Created:
238 aHardDisk->m.state = MediaState_Deleting;
239 break;
240 default:
241 aHardDisk->releaseCaller();
242 return aHardDisk->setStateError();
243 }
244
245 push_front (aHardDisk);
246
247 if (mForward)
248 {
249 /* we will need parent to reparent target */
250 if (!aHardDisk->mParent.isNull())
251 {
252 rc = aHardDisk->mParent->addCaller();
253 CheckComRCReturnRC (rc);
254
255 mParent = aHardDisk->mParent;
256 }
257 }
258 else
259 {
260 /* we will need to reparent children */
261 for (List::const_iterator it = aHardDisk->children().begin();
262 it != aHardDisk->children().end(); ++ it)
263 {
264 rc = (*it)->addCaller();
265 CheckComRCReturnRC (rc);
266
267 rc = (*it)->LockWrite (NULL);
268 if (FAILED (rc))
269 {
270 (*it)->releaseCaller();
271 return rc;
272 }
273
274 mChildren.push_back (*it);
275 }
276 }
277
278 return S_OK;
279 }
280
281 HRESULT addTarget (HardDisk2 *aHardDisk)
282 {
283 HRESULT rc = aHardDisk->addCaller();
284 CheckComRCReturnRC (rc);
285
286 AutoWriteLock alock (aHardDisk);
287
288 if (!mForward)
289 {
290 rc = checkChildrenAndImmutable (aHardDisk);
291 if (FAILED (rc))
292 {
293 aHardDisk->releaseCaller();
294 return rc;
295 }
296 }
297
298 /* go to LockedWrite */
299 rc = aHardDisk->LockWrite (NULL);
300 if (FAILED (rc))
301 {
302 aHardDisk->releaseCaller();
303 return rc;
304 }
305
306 push_front (aHardDisk);
307
308 return S_OK;
309 }
310
311 HRESULT addIntermediate (HardDisk2 *aHardDisk)
312 {
313 HRESULT rc = aHardDisk->addCaller();
314 CheckComRCReturnRC (rc);
315
316 AutoWriteLock alock (aHardDisk);
317
318 rc = checkChildrenAndAttachments (aHardDisk);
319 if (FAILED (rc))
320 {
321 aHardDisk->releaseCaller();
322 return rc;
323 }
324
325 /* go to Deleting */
326 switch (aHardDisk->m.state)
327 {
328 case MediaState_Created:
329 aHardDisk->m.state = MediaState_Deleting;
330 break;
331 default:
332 aHardDisk->releaseCaller();
333 return aHardDisk->setStateError();
334 }
335
336 push_front (aHardDisk);
337
338 return S_OK;
339 }
340
341 bool isForward() const { return mForward; }
342 HardDisk2 *parent() const { return mParent; }
343 const List &children() const { return mChildren; }
344
345 HardDisk2 *source() const
346 { AssertReturn (size() > 0, NULL); return mForward ? front() : back(); }
347
348 HardDisk2 *target() const
349 { AssertReturn (size() > 0, NULL); return mForward ? back() : front(); }
350
351protected:
352
353 // SupportErrorInfoBase interface
354 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk2); }
355 const char *componentName() const { return HardDisk2::ComponentName(); }
356
357private:
358
359 HRESULT check (HardDisk2 *aHardDisk, bool aChildren, bool aAttachments,
360 bool aImmutable)
361 {
362 if (aChildren)
363 {
364 /* not going to multi-merge as it's too expensive */
365 if (aHardDisk->children().size() > 1)
366 {
367 return setError (E_FAIL,
368 tr ("Hard disk '%ls' involved in the merge operation "
369 "has more than one child hard disk (%d)"),
370 aHardDisk->m.locationFull.raw(),
371 aHardDisk->children().size());
372 }
373 }
374
375 if (aAttachments && !mIgnoreAttachments)
376 {
377 if (aHardDisk->m.backRefs.size() != 0)
378 return setError (E_FAIL,
379 tr ("Hard disk '%ls' is attached to %d virtual machines"),
380 aHardDisk->m.locationFull.raw(),
381 aHardDisk->m.backRefs.size());
382 }
383
384 if (aImmutable)
385 {
386 if (aHardDisk->mm.type == HardDiskType_Immutable)
387 return setError (E_FAIL,
388 tr ("Hard disk '%ls' is immutable"),
389 aHardDisk->m.locationFull.raw());
390 }
391
392 return S_OK;
393 }
394
395 HRESULT checkChildren (HardDisk2 *aHardDisk)
396 { return check (aHardDisk, true, false, false); }
397
398 HRESULT checkChildrenAndImmutable (HardDisk2 *aHardDisk)
399 { return check (aHardDisk, true, false, true); }
400
401 HRESULT checkChildrenAndAttachments (HardDisk2 *aHardDisk)
402 { return check (aHardDisk, true, true, false); }
403
404 HRESULT checkChildrenAndAttachmentsAndImmutable (HardDisk2 *aHardDisk)
405 { return check (aHardDisk, true, true, true); }
406
407 /** true if forward merge, false if backward */
408 bool mForward : 1;
409 /** true to not perform attachment checks */
410 bool mIgnoreAttachments : 1;
411
412 /** Parent of the source when forward merge (if any) */
413 ComObjPtr <HardDisk2> mParent;
414 /** Children of the source when backward merge (if any) */
415 List mChildren;
416};
417
418////////////////////////////////////////////////////////////////////////////////
419// HardDisk2 class
420////////////////////////////////////////////////////////////////////////////////
421
422// constructor / destructor
423////////////////////////////////////////////////////////////////////////////////
424
425DEFINE_EMPTY_CTOR_DTOR (HardDisk2)
426
427HRESULT HardDisk2::FinalConstruct()
428{
429 /* Initialize the callbacks of the VD error interface */
430 mm.vdIfCallsError.cbSize = sizeof (VDINTERFACEERROR);
431 mm.vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
432 mm.vdIfCallsError.pfnError = vdErrorCall;
433
434 /* Initialize the callbacks of the VD progress interface */
435 mm.vdIfCallsProgress.cbSize = sizeof (VDINTERFACEPROGRESS);
436 mm.vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
437 mm.vdIfCallsProgress.pfnProgress = vdProgressCall;
438
439 /* Initialize the per-disk interface chain */
440 int vrc;
441 vrc = VDInterfaceAdd (&mm.vdIfError,
442 "HardDisk2::vdInterfaceError",
443 VDINTERFACETYPE_ERROR,
444 &mm.vdIfCallsError, this, &mm.vdDiskIfaces);
445 AssertRCReturn (vrc, E_FAIL);
446 vrc = VDInterfaceAdd (&mm.vdIfProgress,
447 "HardDisk2::vdInterfaceProgress",
448 VDINTERFACETYPE_PROGRESS,
449 &mm.vdIfCallsProgress, this, &mm.vdDiskIfaces);
450 AssertRCReturn (vrc, E_FAIL);
451
452 return S_OK;
453}
454
455void HardDisk2::FinalRelease()
456{
457 uninit();
458}
459
460// public initializer/uninitializer for internal purposes only
461////////////////////////////////////////////////////////////////////////////////
462
463/**
464 * Initializes the hard disk object without creating or opening an associated
465 * storage unit.
466 *
467 * @param aVirtualBox VirtualBox object.
468 * @param aLocaiton Storage unit location.
469 */
470HRESULT HardDisk2::init (VirtualBox *aVirtualBox, const BSTR aFormat,
471 const BSTR aLocation)
472{
473 AssertReturn (aVirtualBox != NULL, E_INVALIDARG);
474 AssertReturn (aLocation != NULL, E_INVALIDARG);
475 AssertReturn (aFormat != NULL && *aFormat != '\0', E_INVALIDARG);
476
477 /* Enclose the state transition NotReady->InInit->Ready */
478 AutoInitSpan autoInitSpan (this);
479 AssertReturn (autoInitSpan.isOk(), E_FAIL);
480
481 HRESULT rc = S_OK;
482
483 /* share VirtualBox weakly (parent remains NULL so far) */
484 unconst (mVirtualBox) = aVirtualBox;
485
486 /* register with VirtualBox early, since uninit() will
487 * unconditionally unregister on failure */
488 aVirtualBox->addDependentChild (this);
489
490 /* no storage yet */
491 m.state = MediaState_NotCreated;
492
493 /* No storage unit is created yet, no need to queryInfo() */
494
495 rc = setFormat (aFormat);
496 CheckComRCReturnRC (rc);
497
498 rc = setLocation (aLocation);
499 CheckComRCReturnRC (rc);
500
501 /* Confirm a successful initialization when it's the case */
502 if (SUCCEEDED (rc))
503 autoInitSpan.setSucceeded();
504
505 return rc;
506}
507
508/**
509 * Initializes the hard disk object by opening the storage unit at the specified
510 * location.
511 *
512 * Note that the UUID, format and the parent of this hard disk will be
513 * determined when reading the hard disk storage unit. If the detected parent is
514 * not known to VirtualBox, then this method will fail.
515 *
516 * @param aVirtualBox VirtualBox object.
517 * @param aLocaiton Storage unit location.
518 */
519HRESULT HardDisk2::init (VirtualBox *aVirtualBox, const BSTR aLocation)
520{
521 AssertReturn (aVirtualBox, E_INVALIDARG);
522 AssertReturn (aLocation, E_INVALIDARG);
523
524 /* Enclose the state transition NotReady->InInit->Ready */
525 AutoInitSpan autoInitSpan (this);
526 AssertReturn (autoInitSpan.isOk(), E_FAIL);
527
528 HRESULT rc = S_OK;
529
530 /* share VirtualBox weakly (parent remains NULL so far) */
531 unconst (mVirtualBox) = aVirtualBox;
532
533 /* register with VirtualBox early, since uninit() will
534 * unconditionally unregister on failure */
535 aVirtualBox->addDependentChild (this);
536
537 /* there must be a storage unit */
538 m.state = MediaState_Created;
539
540 rc = setLocation (aLocation);
541 CheckComRCReturnRC (rc);
542
543 /* get all the information about the medium from the storage unit */
544 rc = queryInfo();
545 if (SUCCEEDED (rc))
546 {
547 /* if the storage unit is not accessible, it's not acceptable for the
548 * newly opened media so convert this into an error */
549 if (m.state == MediaState_Inaccessible)
550 {
551 Assert (!m.lastAccessError.isNull());
552 rc = setError (E_FAIL, Utf8Str (m.lastAccessError));
553 }
554
555 /* storage format must be detected by queryInfo() if the medium is
556 * accessible */
557 AssertReturn (!m.id.isEmpty() && !mm.format.isNull(), E_FAIL);
558 }
559
560 /* Confirm a successful initialization when it's the case */
561 if (SUCCEEDED (rc))
562 autoInitSpan.setSucceeded();
563
564 return rc;
565}
566
567/**
568 * Initializes the hard disk object by loading its data from the given settings
569 * node.
570 *
571 * @param aVirtualBox VirtualBox object.
572 * @param aParent Parent hard disk or NULL for a root hard disk.
573 * @param aNode <HardDisk> settings node.
574 *
575 * @note Locks VirtualBox lock for writing, treeLock() for writing.
576 */
577HRESULT HardDisk2::init (VirtualBox *aVirtualBox, HardDisk2 *aParent,
578 const settings::Key &aNode)
579{
580 using namespace settings;
581
582 AssertReturn (aVirtualBox, E_INVALIDARG);
583
584 /* Enclose the state transition NotReady->InInit->Ready */
585 AutoInitSpan autoInitSpan (this);
586 AssertReturn (autoInitSpan.isOk(), E_FAIL);
587
588 HRESULT rc = S_OK;
589
590 /* share VirtualBox and parent weakly */
591 unconst (mVirtualBox) = aVirtualBox;
592
593 /* register with VirtualBox/parent early, since uninit() will
594 * unconditionally unregister on failure */
595 if (aParent == NULL)
596 aVirtualBox->addDependentChild (this);
597 else
598 {
599 /* we set mParent */
600 AutoWriteLock treeLock (this->treeLock());
601
602 mParent = aParent;
603 aParent->addDependentChild (this);
604 }
605
606 /* see below why we don't call queryInfo() (and therefore treat the medium
607 * as inaccessible for now */
608 m.state = MediaState_Inaccessible;
609
610 /* required */
611 unconst (m.id) = aNode.value <Guid> ("uuid");
612
613 /* optional */
614 {
615 settings::Key descNode = aNode.findKey ("Description");
616 if (!descNode.isNull())
617 m.description = descNode.keyStringValue();
618 }
619
620 /* required */
621 Bstr format = aNode.stringValue ("format");
622 AssertReturn (!format.isNull(), E_FAIL);
623 rc = setFormat (format);
624 CheckComRCReturnRC (rc);
625
626 /* required */
627 Bstr location = aNode.stringValue ("location");
628 rc = setLocation (location);
629 CheckComRCReturnRC (rc);
630
631 /* type is only for base hard disks */
632 if (mParent.isNull())
633 {
634 const char *type = aNode.stringValue ("type");
635 if (strcmp (type, "Normal") == 0)
636 mm.type = HardDiskType_Normal;
637 else if (strcmp (type, "Immutable") == 0)
638 mm.type = HardDiskType_Immutable;
639 else if (strcmp (type, "Writethrough") == 0)
640 mm.type = HardDiskType_Writethrough;
641 else
642 AssertFailed();
643 }
644
645 LogFlowThisFunc (("m.location='%ls', mm.format=%ls, m.id={%RTuuid}\n",
646 m.location.raw(), mm.format.raw(), m.id.raw()));
647 LogFlowThisFunc (("m.locationFull='%ls'\n", m.locationFull.raw()));
648
649 /* Don't call queryInfo() for registered media to prevent the calling
650 * thread (i.e. the VirtualBox server startup thread) from an unexpected
651 * freeze but mark it as initially inaccessible instead. The vital UUID,
652 * location and format properties are read from the registry file above; to
653 * get the actual state and the rest of the data, the user will have to call
654 * COMGETTER(State). */
655
656 /* load all children */
657 Key::List hardDisks = aNode.keys ("HardDisk");
658 for (Key::List::const_iterator it = hardDisks.begin();
659 it != hardDisks.end(); ++ it)
660 {
661 ComObjPtr <HardDisk2> hardDisk;
662 hardDisk.createObject();
663 rc = hardDisk->init (aVirtualBox, this, *it);
664 CheckComRCBreakRC (rc);
665
666 rc = mVirtualBox->registerHardDisk2 (hardDisk, false /* aSaveRegistry */);
667 CheckComRCBreakRC (rc);
668 }
669
670 /* Confirm a successful initialization when it's the case */
671 if (SUCCEEDED (rc))
672 autoInitSpan.setSucceeded();
673
674 return rc;
675}
676
677/**
678 * Uninitializes the instance.
679 *
680 * Called either from FinalRelease() or by the parent when it gets destroyed.
681 *
682 * @note All children of this hard disk get uninitialized by calling their
683 * uninit() methods.
684 *
685 * @note Locks treeLock() for writing, VirtualBox for writing.
686 */
687void HardDisk2::uninit()
688{
689 /* Enclose the state transition Ready->InUninit->NotReady */
690 AutoUninitSpan autoUninitSpan (this);
691 if (autoUninitSpan.uninitDone())
692 return;
693
694 if (!mm.formatObj.isNull())
695 {
696 /* remove the caller reference we added in setFormat() */
697 mm.formatObj->releaseCaller();
698 mm.formatObj.setNull();
699 }
700
701 if (m.state == MediaState_Deleting)
702 {
703 /* we are being uninitialized after've been deleted by merge.
704 * Reparenting has already been done so don't touch it here (we are
705 * now orphans and remoeDependentChild() will assert) */
706
707 Assert (mParent.isNull());
708 }
709 else
710 {
711 /* we uninit children and reset mParent
712 * and VirtualBox::removeDependentChild() needs a write lock */
713 AutoMultiWriteLock2 alock (mVirtualBox->lockHandle(), this->treeLock());
714
715 uninitDependentChildren();
716
717 if (!mParent.isNull())
718 {
719 mParent->removeDependentChild (this);
720 mParent.setNull();
721 }
722 else
723 mVirtualBox->removeDependentChild (this);
724 }
725
726 unconst (mVirtualBox).setNull();
727}
728
729// IHardDisk2 properties
730////////////////////////////////////////////////////////////////////////////////
731
732STDMETHODIMP HardDisk2::COMGETTER(Format) (BSTR *aFormat)
733{
734 if (aFormat == NULL)
735 return E_POINTER;
736
737 AutoCaller autoCaller (this);
738 CheckComRCReturnRC (autoCaller.rc());
739
740 /* no need to lock, mm.format is const */
741 mm.format.cloneTo (aFormat);
742
743 return S_OK;
744}
745
746STDMETHODIMP HardDisk2::COMGETTER(Type) (HardDiskType_T *aType)
747{
748 if (aType == NULL)
749 return E_POINTER;
750
751 AutoCaller autoCaller (this);
752 CheckComRCReturnRC (autoCaller.rc());
753
754 AutoReadLock alock (this);
755
756 *aType = mm.type;
757
758 return S_OK;
759}
760
761STDMETHODIMP HardDisk2::COMSETTER(Type) (HardDiskType_T aType)
762{
763 AutoCaller autoCaller (this);
764 CheckComRCReturnRC (autoCaller.rc());
765
766 /* VirtualBox::saveSettings() needs a write lock */
767 AutoMultiWriteLock2 alock (mVirtualBox, this);
768
769 switch (m.state)
770 {
771 case MediaState_Created:
772 case MediaState_Inaccessible:
773 break;
774 default:
775 return setStateError();
776 }
777
778 if (mm.type == aType)
779 {
780 /* Nothing to do */
781 return S_OK;
782 }
783
784 /* we access mParent & children() */
785 AutoReadLock treeLock (this->treeLock());
786
787 /* cannot change the type of a differencing hard disk */
788 if (!mParent.isNull())
789 return setError (E_FAIL,
790 tr ("Hard disk '%ls' is a differencing hard disk"),
791 m.locationFull.raw());
792
793 /* cannot change the type of a hard disk being in use */
794 if (m.backRefs.size() != 0)
795 return setError (E_FAIL,
796 tr ("Hard disk '%ls' is attached to %d virtual machines"),
797 m.locationFull.raw(), m.backRefs.size());
798
799 switch (aType)
800 {
801 case HardDiskType_Normal:
802 case HardDiskType_Immutable:
803 {
804 /* normal can be easily converted to imutable and vice versa even
805 * if they have children as long as they are not attached to any
806 * machine themselves */
807 break;
808 }
809 case HardDiskType_Writethrough:
810 {
811 /* cannot change to writethrough if there are children */
812 if (children().size() != 0)
813 return setError (E_FAIL,
814 tr ("Hard disk '%ls' has %d child hard disks"),
815 children().size());
816 break;
817 }
818 default:
819 AssertFailedReturn (E_FAIL);
820 }
821
822 mm.type = aType;
823
824 HRESULT rc = mVirtualBox->saveSettings();
825
826 return rc;
827}
828
829STDMETHODIMP HardDisk2::COMGETTER(Parent) (IHardDisk2 **aParent)
830{
831 if (aParent == NULL)
832 return E_POINTER;
833
834 AutoCaller autoCaller (this);
835 CheckComRCReturnRC (autoCaller.rc());
836
837 /* we access mParent */
838 AutoReadLock treeLock (this->treeLock());
839
840 mParent.queryInterfaceTo (aParent);
841
842 return S_OK;
843}
844
845STDMETHODIMP HardDisk2::COMGETTER(Children) (ComSafeArrayOut (IHardDisk2 *, aChildren))
846{
847 if (ComSafeArrayOutIsNull (aChildren))
848 return E_POINTER;
849
850 AutoCaller autoCaller (this);
851 CheckComRCReturnRC (autoCaller.rc());
852
853 /* we access children */
854 AutoReadLock treeLock (this->treeLock());
855
856 SafeIfaceArray <IHardDisk2> children (this->children());
857 children.detachTo (ComSafeArrayOutArg (aChildren));
858
859 return S_OK;
860}
861
862STDMETHODIMP HardDisk2::COMGETTER(Root) (IHardDisk2 **aRoot)
863{
864 if (aRoot == NULL)
865 return E_POINTER;
866
867 /* root() will do callers/locking */
868
869 root().queryInterfaceTo (aRoot);
870
871 return S_OK;
872}
873
874STDMETHODIMP HardDisk2::COMGETTER(ReadOnly) (BOOL *aReadOnly)
875{
876 if (aReadOnly == NULL)
877 return E_POINTER;
878
879 AutoCaller autoCaller (this);
880 CheckComRCReturnRC (autoCaller.rc());
881
882 /* isRadOnly() will do locking */
883
884 *aReadOnly = isReadOnly();
885
886 return S_OK;
887}
888
889STDMETHODIMP HardDisk2::COMGETTER(LogicalSize) (ULONG64 *aLogicalSize)
890{
891 if (aLogicalSize == NULL)
892 return E_POINTER;
893
894 {
895 AutoCaller autoCaller (this);
896 CheckComRCReturnRC (autoCaller.rc());
897
898 AutoReadLock alock (this);
899
900 /* we access mParent */
901 AutoReadLock treeLock (this->treeLock());
902
903 if (mParent.isNull())
904 {
905 *aLogicalSize = mm.logicalSize;
906
907 return S_OK;
908 }
909 }
910
911 /* We assume that some backend may decide to return a meaningless value in
912 * response to VDGetSize() for differencing hard disks and therefore
913 * always ask the base hard disk ourselves. */
914
915 /* root() will do callers/locking */
916
917 return root()->COMGETTER (LogicalSize) (aLogicalSize);
918}
919
920// IHardDisk2 methods
921////////////////////////////////////////////////////////////////////////////////
922
923STDMETHODIMP HardDisk2::GetProperty (INPTR BSTR aName, BSTR *aValue)
924{
925 CheckComArgStrNotEmptyOrNull (aName);
926 CheckComArgOutPointerValid (aValue);
927
928 AutoCaller autoCaller (this);
929 CheckComRCReturnRC (autoCaller.rc());
930
931 AutoReadLock alock (this);
932
933 Data::PropertyMap::const_iterator it = mm.properties.find (Bstr (aName));
934 if (it == mm.properties.end())
935 return setError (VBOX_E_OBJECT_NOT_FOUND,
936 tr ("Property '%ls' does not exist"), aName);
937
938 it->second.cloneTo (aValue);
939
940 return S_OK;
941}
942
943STDMETHODIMP HardDisk2::SetProperty (INPTR BSTR aName, INPTR BSTR aValue)
944{
945 CheckComArgStrNotEmptyOrNull (aName);
946
947 AutoWriteLock alock (this);
948
949 Data::PropertyMap::iterator it = mm.properties.find (Bstr (aName));
950 if (it == mm.properties.end())
951 return setError (VBOX_E_OBJECT_NOT_FOUND,
952 tr ("Property '%ls' does not exist"), aName);
953
954 it->second = aValue;
955
956 return S_OK;
957}
958
959STDMETHODIMP HardDisk2::GetProperties (INPTR BSTR aNames,
960 ComSafeArrayOut (BSTR, aReturnNames),
961 ComSafeArrayOut (BSTR, aReturnValues))
962{
963 CheckComArgOutSafeArrayPointerValid (aReturnNames);
964 CheckComArgOutSafeArrayPointerValid (aReturnValues);
965
966 AutoCaller autoCaller (this);
967 CheckComRCReturnRC (autoCaller.rc());
968
969 AutoReadLock alock (this);
970
971 com::SafeArray <BSTR> names (mm.properties.size());
972 com::SafeArray <BSTR> values (mm.properties.size());
973 size_t i = 0;
974
975 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
976 it != mm.properties.end(); ++ it)
977 {
978 it->first.cloneTo (&names [i]);
979 it->second.cloneTo (&values [i]);
980 ++ i;
981 }
982
983 names.detachTo (ComSafeArrayOutArg (aReturnNames));
984 values.detachTo (ComSafeArrayOutArg (aReturnValues));
985
986 return S_OK;
987}
988
989STDMETHODIMP HardDisk2::CreateDynamicStorage (ULONG64 aLogicalSize,
990 IProgress **aProgress)
991{
992 if (aProgress == NULL)
993 return E_POINTER;
994
995 AutoCaller autoCaller (this);
996 CheckComRCReturnRC (autoCaller.rc());
997
998 AutoWriteLock alock (this);
999
1000 switch (m.state)
1001 {
1002 case MediaState_NotCreated:
1003 break;
1004 default:
1005 return setStateError();
1006 }
1007
1008 /// @todo NEWMEDIA use backend capabilities to decide if dynamic storage
1009 /// is supported
1010
1011 ComObjPtr <Progress> progress;
1012 progress.createObject();
1013 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1014 BstrFmt (tr ("Creating dynamic hard disk storage unit '%ls'"),
1015 m.location.raw()),
1016 FALSE /* aCancelable */);
1017 CheckComRCReturnRC (rc);
1018
1019 /* setup task object and thread to carry out the operation
1020 * asynchronously */
1021
1022 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDynamic));
1023 AssertComRCReturnRC (task->autoCaller.rc());
1024
1025 task->d.size = aLogicalSize;
1026
1027 rc = task->startThread();
1028 CheckComRCReturnRC (rc);
1029
1030 /* go to Creating state on success */
1031 m.state = MediaState_Creating;
1032
1033 /* task is now owned by taskThread() so release it */
1034 task.release();
1035
1036 /* return progress to the caller */
1037 progress.queryInterfaceTo (aProgress);
1038
1039 return S_OK;
1040}
1041
1042STDMETHODIMP HardDisk2::CreateFixedStorage (ULONG64 aLogicalSize,
1043 IProgress **aProgress)
1044{
1045 if (aProgress == NULL)
1046 return E_POINTER;
1047
1048 AutoCaller autoCaller (this);
1049 CheckComRCReturnRC (autoCaller.rc());
1050
1051 AutoWriteLock alock (this);
1052
1053 switch (m.state)
1054 {
1055 case MediaState_NotCreated:
1056 break;
1057 default:
1058 return setStateError();
1059 }
1060
1061 ComObjPtr <Progress> progress;
1062 progress.createObject();
1063 HRESULT rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1064 BstrFmt (tr ("Creating fixed hard disk storage unit '%ls'"),
1065 m.location.raw()),
1066 FALSE /* aCancelable */);
1067 CheckComRCReturnRC (rc);
1068
1069 /* setup task object and thread to carry out the operation
1070 * asynchronously */
1071
1072 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateFixed));
1073 AssertComRCReturnRC (task->autoCaller.rc());
1074
1075 task->d.size = aLogicalSize;
1076
1077 rc = task->startThread();
1078 CheckComRCReturnRC (rc);
1079
1080 /* go to Creating state on success */
1081 m.state = MediaState_Creating;
1082
1083 /* task is now owned by taskThread() so release it */
1084 task.release();
1085
1086 /* return progress to the caller */
1087 progress.queryInterfaceTo (aProgress);
1088
1089 return S_OK;
1090}
1091
1092STDMETHODIMP HardDisk2::DeleteStorage (IProgress **aProgress)
1093{
1094 if (aProgress == NULL)
1095 return E_POINTER;
1096
1097 ComObjPtr <Progress> progress;
1098
1099 HRESULT rc = deleteStorageNoWait (progress);
1100 if (SUCCEEDED (rc))
1101 {
1102 /* return progress to the caller */
1103 progress.queryInterfaceTo (aProgress);
1104 }
1105
1106 return rc;
1107}
1108
1109STDMETHODIMP HardDisk2::CreateDiffStorage (IHardDisk2 *aTarget, IProgress **aProgress)
1110{
1111 if (aTarget == NULL)
1112 return E_INVALIDARG;
1113 if (aProgress == NULL)
1114 return E_POINTER;
1115
1116 AutoCaller autoCaller (this);
1117 CheckComRCReturnRC (autoCaller.rc());
1118
1119 ComObjPtr <HardDisk2> diff;
1120 HRESULT rc = mVirtualBox->cast (aTarget, diff);
1121 CheckComRCReturnRC (rc);
1122
1123 AutoWriteLock alock (this);
1124
1125 if (mm.type == HardDiskType_Writethrough)
1126 return setError (E_FAIL, tr ("Hard disk '%ls' is Writethrough"));
1127
1128 /* We want to be locked for reading as long as our diff child is being
1129 * created */
1130 rc = LockRead (NULL);
1131 CheckComRCReturnRC (rc);
1132
1133 ComObjPtr <Progress> progress;
1134
1135 rc = createDiffStorageNoWait (diff, progress);
1136 if (FAILED (rc))
1137 {
1138 HRESULT rc2 = UnlockRead (NULL);
1139 AssertComRC (rc2);
1140 /* Note: on success, taskThread() will unlock this */
1141 }
1142 else
1143 {
1144 /* return progress to the caller */
1145 progress.queryInterfaceTo (aProgress);
1146 }
1147
1148 return rc;
1149}
1150
1151STDMETHODIMP HardDisk2::MergeTo (INPTR GUIDPARAM aTargetId, IProgress **aProgress)
1152{
1153 AutoCaller autoCaller (this);
1154 CheckComRCReturnRC (autoCaller.rc());
1155
1156 return E_NOTIMPL;
1157}
1158
1159STDMETHODIMP HardDisk2::CloneTo (IHardDisk2 *aTarget, IProgress **aProgress)
1160{
1161 AutoCaller autoCaller (this);
1162 CheckComRCReturnRC (autoCaller.rc());
1163
1164 return E_NOTIMPL;
1165}
1166
1167STDMETHODIMP HardDisk2::FlattenTo (IHardDisk2 *aTarget, IProgress **aProgress)
1168{
1169 AutoCaller autoCaller (this);
1170 CheckComRCReturnRC (autoCaller.rc());
1171
1172 return E_NOTIMPL;
1173}
1174
1175// public methods for internal purposes only
1176////////////////////////////////////////////////////////////////////////////////
1177
1178/**
1179 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
1180 * of this hard disk or any its child and updates the paths if necessary to
1181 * reflect the new location.
1182 *
1183 * @param aOldPath Old path (full).
1184 * @param aNewPath New path (full).
1185 *
1186 * @note Locks treeLock() for reading, this object and all children for writing.
1187 */
1188void HardDisk2::updatePaths (const char *aOldPath, const char *aNewPath)
1189{
1190 AssertReturnVoid (aOldPath);
1191 AssertReturnVoid (aNewPath);
1192
1193 AutoCaller autoCaller (this);
1194 AssertComRCReturnVoid (autoCaller.rc());
1195
1196 AutoWriteLock alock (this);
1197
1198 /* we access children() */
1199 AutoReadLock treeLock (this->treeLock());
1200
1201 updatePath (aOldPath, aNewPath);
1202
1203 /* update paths of all children */
1204 for (List::const_iterator it = children().begin();
1205 it != children().end();
1206 ++ it)
1207 {
1208 (*it)->updatePaths (aOldPath, aNewPath);
1209 }
1210}
1211
1212/**
1213 * Returns the base hard disk of the hard disk chain this hard disk is part of.
1214 *
1215 * The root hard disk is found by walking up the parent-child relationship axis.
1216 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
1217 * returns itself in response to this method.
1218 *
1219 * @param aLevel Where to store the number of ancestors of this hard disk
1220 * (zero for the root), may be @c NULL.
1221 *
1222 * @note Locks treeLock() for reading.
1223 */
1224ComObjPtr <HardDisk2> HardDisk2::root (uint32_t *aLevel /*= NULL*/)
1225{
1226 ComObjPtr <HardDisk2> root;
1227 uint32_t level;
1228
1229 AutoCaller autoCaller (this);
1230 AssertReturn (autoCaller.isOk(), root);
1231
1232 /* we access mParent */
1233 AutoReadLock treeLock (this->treeLock());
1234
1235 root = this;
1236 level = 0;
1237
1238 if (!mParent.isNull())
1239 {
1240 for (;;)
1241 {
1242 AutoCaller rootCaller (root);
1243 AssertReturn (rootCaller.isOk(), root);
1244
1245 if (root->mParent.isNull())
1246 break;
1247
1248 root = root->mParent;
1249 ++ level;
1250 }
1251 }
1252
1253 if (aLevel != NULL)
1254 *aLevel = level;
1255
1256 return root;
1257}
1258
1259/**
1260 * Returns @c true if this hard disk cannot be modified because it has
1261 * dependants (children) or is part of the snapshot. Related to the hard disk
1262 * type and posterity, not to the current media state.
1263 *
1264 * @note Locks this object and treeLock() for reading.
1265 */
1266bool HardDisk2::isReadOnly()
1267{
1268 AutoCaller autoCaller (this);
1269 AssertComRCReturn (autoCaller.rc(), false);
1270
1271 AutoReadLock alock (this);
1272
1273 /* we access children */
1274 AutoReadLock treeLock (this->treeLock());
1275
1276 switch (mm.type)
1277 {
1278 case HardDiskType_Normal:
1279 {
1280 if (children().size() != 0)
1281 return true;
1282
1283 for (BackRefList::const_iterator it = m.backRefs.begin();
1284 it != m.backRefs.end(); ++ it)
1285 if (it->snapshotIds.size() != 0)
1286 return true;
1287
1288 return false;
1289 }
1290 case HardDiskType_Immutable:
1291 {
1292 return true;
1293 }
1294 case HardDiskType_Writethrough:
1295 {
1296 return false;
1297 }
1298 default:
1299 break;
1300 }
1301
1302 AssertFailedReturn (false);
1303}
1304
1305/**
1306 * Saves hard disk data by appending a new <HardDisk> child node to the given
1307 * parent node which can be either <HardDisks> or <HardDisk>.
1308 *
1309 * @param aaParentNode Parent <HardDisks> or <HardDisk> node.
1310 *
1311 * @note Locks this object, treeLock() and children for reading.
1312 */
1313HRESULT HardDisk2::saveSettings (settings::Key &aParentNode)
1314{
1315 using namespace settings;
1316
1317 AssertReturn (!aParentNode.isNull(), E_FAIL);
1318
1319 AutoCaller autoCaller (this);
1320 CheckComRCReturnRC (autoCaller.rc());
1321
1322 AutoReadLock alock (this);
1323
1324 /* we access mParent */
1325 AutoReadLock treeLock (this->treeLock());
1326
1327 Key diskNode = aParentNode.appendKey ("HardDisk");
1328 /* required */
1329 diskNode.setValue <Guid> ("uuid", m.id);
1330 /* required (note: the original locaiton, not full) */
1331 diskNode.setValue <Bstr> ("location", m.location);
1332 /* required */
1333 diskNode.setValue <Bstr> ("format", mm.format);
1334 /* optional */
1335 if (!m.description.isNull())
1336 {
1337 Key descNode = diskNode.createKey ("Description");
1338 descNode.setKeyValue <Bstr> (m.description);
1339 }
1340
1341 /* only for base hard disks */
1342 if (mParent.isNull())
1343 {
1344 const char *type =
1345 mm.type == HardDiskType_Normal ? "Normal" :
1346 mm.type == HardDiskType_Immutable ? "Immutable" :
1347 mm.type == HardDiskType_Writethrough ? "Writethrough" : NULL;
1348 Assert (type != NULL);
1349 diskNode.setStringValue ("type", type);
1350 }
1351
1352 /* save all children */
1353 for (List::const_iterator it = children().begin();
1354 it != children().end();
1355 ++ it)
1356 {
1357 HRESULT rc = (*it)->saveSettings (diskNode);
1358 AssertComRCReturnRC (rc);
1359 }
1360
1361 return S_OK;
1362}
1363
1364/**
1365 * Compares the location of this hard disk to the given location.
1366 *
1367 * The comparison takes the location details into account. For example, if the
1368 * location is a file in the host's filesystem, a case insensitive comparison
1369 * will be performed for case insensitive filesystems.
1370 *
1371 * @param aLocation Location to compare to (as is).
1372 * @param aResult Where to store the result of comparison: 0 if locations
1373 * are equal, 1 if this object's location is greater than
1374 * the specified location, and -1 otherwise.
1375 */
1376HRESULT HardDisk2::compareLocationTo (const char *aLocation, int &aResult)
1377{
1378 AutoCaller autoCaller (this);
1379 AssertComRCReturnRC (autoCaller.rc());
1380
1381 AutoReadLock alock (this);
1382
1383 Utf8Str locationFull (m.locationFull);
1384
1385 /// @todo NEWMEDIA delegate the comparison to the backend?
1386
1387 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
1388 {
1389 Utf8Str location (aLocation);
1390
1391 /* For locations represented by files, append the default path if
1392 * only the name is given, and then get the full path. */
1393 if (!RTPathHavePath (aLocation))
1394 {
1395 AutoReadLock propsLock (mVirtualBox->systemProperties());
1396 location = Utf8StrFmt ("%ls%c%s",
1397 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
1398 RTPATH_DELIMITER, aLocation);
1399 }
1400
1401 int vrc = mVirtualBox->calculateFullPath (location, location);
1402 if (RT_FAILURE (vrc))
1403 return setError (E_FAIL,
1404 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
1405 location.raw(), vrc);
1406
1407 aResult = RTPathCompare (locationFull, location);
1408 }
1409 else
1410 aResult = locationFull.compare (aLocation);
1411
1412 return S_OK;
1413}
1414
1415/**
1416 * Returns a short version of the location attribute.
1417 *
1418 * Reimplements MediumBase::name() to specially treat non-FS-path locations.
1419 *
1420 * @note Must be called from under this object's read or write lock.
1421 */
1422Utf8Str HardDisk2::name()
1423{
1424 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to requiest
1425 /// this information from the VD backend)
1426
1427 Utf8Str location (m.locationFull);
1428
1429 Utf8Str name = RTPathFilename (location);
1430 return name;
1431}
1432
1433/// @todo NEWMEDIA remove, should not need anymore
1434#if 0
1435
1436/**
1437 * Returns @c true if the given location is a file in the host's filesystem.
1438 *
1439 * @param aLocation Location to check.
1440 */
1441/*static*/
1442bool HardDisk2::isFileLocation (const char *aLocation)
1443{
1444 /// @todo NEWMEDIA need some library that deals with URLs in a generic way
1445
1446 if (aLocation == NULL)
1447 return false;
1448
1449 size_t len = strlen (aLocation);
1450
1451 /* unix-like paths */
1452 if (len >= 1 && RTPATH_IS_SLASH (aLocation [0]))
1453 return true;
1454
1455 /* dos-like paths */
1456 if (len >= 2 && RTPATH_IS_VOLSEP (aLocation [1]) &&
1457 ((aLocation [0] >= 'A' && aLocation [0] <= 'Z') ||
1458 (aLocation [0] >= 'a' && aLocation [0] <= 'z')))
1459 return true;
1460
1461 /* if there is a '<protocol>:' suffux which is not 'file:', return false */
1462 const char *s = strchr (aLocation, ':');
1463 if (s != NULL)
1464 {
1465 if (s - aLocation != 4)
1466 return false;
1467 if ((aLocation [0] != 'f' && aLocation [0] == 'F') ||
1468 (aLocation [1] != 'i' && aLocation [1] == 'I') ||
1469 (aLocation [2] != 'l' && aLocation [2] == 'L') ||
1470 (aLocation [3] != 'e' && aLocation [3] == 'E'))
1471 return false;
1472 }
1473
1474 /* everything else is treaten as file */
1475 return true;
1476}
1477
1478#endif /* if 0 */
1479
1480/**
1481 * Checks that this hard disk may be discarded and performs necessary state
1482 * changes.
1483 *
1484 * This method is to be called prior to calling the #discrad() to perform
1485 * necessary consistency checks and place involved hard disks to appropriate
1486 * states. If #discard() is not called or fails, the state modifications
1487 * performed by this method must be undone by #cancelDiscard().
1488 *
1489 * See #discard() for more info about discarding hard disks.
1490 *
1491 * @param aChain Where to store the created merge chain (may return NULL
1492 * if no real merge is necessary).
1493 *
1494 * @note Locks treeLock() for reading. Locks this object, aTarget and all
1495 * intermediate hard disks for writing.
1496 */
1497HRESULT HardDisk2::prepareDiscard (MergeChain * &aChain)
1498{
1499 AutoCaller autoCaller (this);
1500 AssertComRCReturnRC (autoCaller.rc());
1501
1502 aChain = NULL;
1503
1504 AutoWriteLock alock (this);
1505
1506 /* we access mParent & children() */
1507 AutoReadLock treeLock (this->treeLock());
1508
1509 AssertReturn (mm.type == HardDiskType_Normal, E_FAIL);
1510
1511 if (children().size() == 0)
1512 {
1513 /* special treatment of the last hard disk in the chain: */
1514
1515 if (mParent.isNull())
1516 {
1517 /* lock only, to prevent any usage; discard() will unlock */
1518 return LockWrite (NULL);
1519 }
1520
1521 /* the differencing hard disk w/o children will be deleted, protect it
1522 * from attaching to other VMs (this is why Deleting) */
1523
1524 switch (m.state)
1525 {
1526 case MediaState_Created:
1527 m.state = MediaState_Deleting;
1528 break;
1529 default:
1530 return setStateError();
1531 }
1532
1533 /* aChain is intentionally NULL here */
1534
1535 return S_OK;
1536 }
1537
1538 /* not going multi-merge as it's too expensive */
1539 if (children().size() > 1)
1540 return setError (E_FAIL,
1541 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
1542 m.locationFull.raw(), children().size());
1543
1544 /* this is a read-only hard disk with children; it must be associated with
1545 * exactly one snapshot (when the snapshot is being taken, none of the
1546 * current VM's hard disks may be attached to other VMs). Note that by the
1547 * time when discard() is called, there must be no any attachments at all
1548 * (the code calling prepareDiscard() should detach). */
1549 AssertReturn (m.backRefs.size() == 1 &&
1550 !m.backRefs.front().inCurState &&
1551 m.backRefs.front().snapshotIds.size() == 1, E_FAIL);
1552
1553 ComObjPtr <HardDisk2> child = children().front();
1554
1555 /* we keep this locked, so lock the affected child to make sure the lock
1556 * order is correct when calling prepareMergeTo() */
1557 AutoWriteLock childLock (child);
1558
1559 /* delegate the rest to the profi */
1560 if (mParent.isNull())
1561 {
1562 /* base hard disk, backward merge */
1563
1564 Assert (child->m.backRefs.size() == 1);
1565 if (child->m.backRefs.front().machineId != m.backRefs.front().machineId)
1566 {
1567 /* backward merge is too tricky, we'll just detach on discard, so
1568 * lock only, to prevent any usage; discard() will only unlock
1569 * (since we return NULL in aChain) */
1570 return LockWrite (NULL);
1571 }
1572
1573 return child->prepareMergeTo (this, aChain,
1574 true /* aIgnoreAttachments */);
1575 }
1576 else
1577 {
1578 /* forward merge */
1579 return prepareMergeTo (child, aChain,
1580 true /* aIgnoreAttachments */);
1581 }
1582}
1583
1584/**
1585 * Discards this hard disk.
1586 *
1587 * Discarding the hard disk is merging its contents to its differencing child
1588 * hard disk (forward merge) or contents of its child hard disk to itself
1589 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
1590 * a differencing hard disk w/o children, then it will be simply deleted.
1591 * Calling this method on a base hard disk w/o children will do nothing and
1592 * silently succeed. If this hard disk has more than one child, the method will
1593 * currently return an error (since merging in this case would be too expensive
1594 * and result in data duplication).
1595 *
1596 * When the backward merge takes place (i.e. this hard disk is a target) then,
1597 * on success, this hard disk will automatically replace the differencing child
1598 * hard disk used as a source (which will then be deleted) in the attachment
1599 * this child hard disk is associated with. This will happen only if both hard
1600 * disks belong to the same machine because otherwise such a replace would be
1601 * too tricky and could be not expected by the other machine. Same relates to a
1602 * case when the child hard disk is not associated with any machine at all. When
1603 * the backward merge is not applied, the method behaves as if the base hard
1604 * disk were not attached at all -- i.e. simply detaches it from the machine but
1605 * leaves the hard disk chain intact.
1606 *
1607 * This method is basically a wrapper around #mergeTo() that selects the correct
1608 * merge direction and performs additional actions as described above and.
1609 *
1610 * Note that this method will not return until the merge operation is complete
1611 * (which may be quite time consuming depending on the size of the merged hard
1612 * disks).
1613 *
1614 * Note that #prepareDiscard() must be called before calling this method. If
1615 * this method returns a failure, the caller must call #cancelDiscard(). On
1616 * success, #cancelDiscard() must not be called (this method will perform all
1617 * necessary steps such as resetting states of all involved hard disks and
1618 * deleting @a aChain).
1619 *
1620 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1621 * no real merge takes place).
1622 *
1623 * @note Locks the hard disks from the chain for writing. Locks the machine
1624 * object when the backward merge takes place. Locks treeLock() lock for
1625 * reading or writing.
1626 */
1627HRESULT HardDisk2::discard (ComObjPtr <Progress> &aProgress, MergeChain *aChain)
1628{
1629 AssertReturn (!aProgress.isNull(), E_FAIL);
1630
1631 ComObjPtr <HardDisk2> hdFrom;
1632
1633 HRESULT rc = S_OK;
1634
1635 {
1636 AutoCaller autoCaller (this);
1637 AssertComRCReturnRC (autoCaller.rc());
1638
1639 aProgress->advanceOperation (BstrFmt (
1640 tr ("Discarding hard disk '%s'"), name().raw()));
1641
1642 if (aChain == NULL)
1643 {
1644 AutoWriteLock alock (this);
1645
1646 /* we access mParent & children() */
1647 AutoReadLock treeLock (this->treeLock());
1648
1649 Assert (children().size() == 0);
1650
1651 /* special treatment of the last hard disk in the chain: */
1652
1653 if (mParent.isNull())
1654 {
1655 rc = UnlockWrite (NULL);
1656 AssertComRC (rc);
1657 return rc;
1658 }
1659
1660 /* delete the differencing hard disk w/o children */
1661
1662 Assert (m.state == MediaState_Deleting);
1663
1664 /* go back to Created since deleteStorage() expects this state */
1665 m.state = MediaState_Created;
1666
1667 hdFrom = this;
1668
1669 rc = deleteStorageAndWait (&aProgress);
1670 }
1671 else
1672 {
1673 hdFrom = aChain->source();
1674
1675 rc = hdFrom->mergeToAndWait (aChain, &aProgress);
1676 }
1677 }
1678
1679 if (SUCCEEDED (rc))
1680 {
1681 /* mergeToAndWait() cannot uninitialize the initiator because of
1682 * possible AutoCallers on the current thread, deleteStorageAndWait()
1683 * doesn't do it either; do it ourselves */
1684 hdFrom->uninit();
1685 }
1686
1687 return rc;
1688}
1689
1690/**
1691 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
1692 * or fails. Frees memory occupied by @a aChain.
1693 *
1694 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1695 * no real merge takes place).
1696 *
1697 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
1698 * reading.
1699 */
1700void HardDisk2::cancelDiscard (MergeChain *aChain)
1701{
1702 AutoCaller autoCaller (this);
1703 AssertComRCReturnVoid (autoCaller.rc());
1704
1705 if (aChain == NULL)
1706 {
1707 AutoWriteLock alock (this);
1708
1709 /* we access mParent & children() */
1710 AutoReadLock treeLock (this->treeLock());
1711
1712 Assert (children().size() == 0);
1713
1714 /* special treatment of the last hard disk in the chain: */
1715
1716 if (mParent.isNull())
1717 {
1718 HRESULT rc = UnlockWrite (NULL);
1719 AssertComRC (rc);
1720 return;
1721 }
1722
1723 /* the differencing hard disk w/o children will be deleted, protect it
1724 * from attaching to other VMs (this is why Deleting) */
1725
1726 Assert (m.state == MediaState_Deleting);
1727 m.state = MediaState_Created;
1728
1729 return;
1730 }
1731
1732 /* delegate the rest to the profi */
1733 cancelMergeTo (aChain);
1734}
1735
1736/**
1737 * Returns a preferred format for differencing hard disks.
1738 */
1739Bstr HardDisk2::preferredDiffFormat()
1740{
1741 Bstr format;
1742
1743 AutoCaller autoCaller (this);
1744 AssertComRCReturn (autoCaller.rc(), format);
1745
1746 /* mm.format is const, no need to lock */
1747 format = mm.format;
1748
1749 /* check that our own format supports diffs */
1750 if (!(mm.formatObj->capabilities() & HardDiskFormatCapabilities_Differencing))
1751 {
1752 /* use the default format if not */
1753 AutoReadLock propsLock (mVirtualBox->systemProperties());
1754 format = mVirtualBox->systemProperties()->defaultHardDiskFormat();
1755 }
1756
1757 return format;
1758}
1759
1760// protected methods
1761////////////////////////////////////////////////////////////////////////////////
1762
1763/**
1764 * Deletes the hard disk storage unit.
1765 *
1766 * If @a aProgress is not NULL but the object it points to is @c null then a new
1767 * progress object will be created and assigned to @a *aProgress on success,
1768 * otherwise the existing progress object is used. If Progress is NULL, then no
1769 * progress object is created/used at all.
1770 *
1771 * When @a aWait is @c false, this method will create a thread to perform the
1772 * delete operation asynchronously and will return immediately. Otherwise, it
1773 * will perform the operation on the calling thread and will not return to the
1774 * caller until the operation is completed. Note that @a aProgress cannot be
1775 * NULL when @a aWait is @c false (this method will assert in this case).
1776 *
1777 * @param aProgress Where to find/store a Progress object to track operation
1778 * completion.
1779 * @param aWait @c true if this method should block instead of creating
1780 * an asynchronous thread.
1781 *
1782 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
1783 * writing.
1784 */
1785HRESULT HardDisk2::deleteStorage (ComObjPtr <Progress> *aProgress, bool aWait)
1786{
1787 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
1788
1789 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
1790 * ourselves atomically after detecting that deletion is possible to make
1791 * sure that we don't do that after another thread has done
1792 * VirtualBox::findHardDisk2() but before it starts using us (provided that
1793 * it holds a mVirtualBox lock too of course). */
1794
1795 AutoWriteLock vboxLock (mVirtualBox);
1796
1797 AutoWriteLock alock (this);
1798
1799 switch (m.state)
1800 {
1801 case MediaState_Created:
1802 break;
1803 default:
1804 return setStateError();
1805 }
1806
1807 if (m.backRefs.size() != 0)
1808 return setError (E_FAIL,
1809 tr ("Hard disk '%ls' is attached to %d virtual machines"),
1810 m.locationFull.raw(), m.backRefs.size());
1811
1812 HRESULT rc = canClose();
1813 CheckComRCReturnRC (rc);
1814
1815 /* go to Deleting state before leaving the lock */
1816 m.state = MediaState_Deleting;
1817
1818 /* we need to leave this object's write lock now because of
1819 * unregisterWithVirtualBox() that locks treeLock() for writing */
1820 alock.leave();
1821
1822 /* try to remove from the list of known hard disks before performing actual
1823 * deletion (we favor the consistency of the media registry in the first
1824 * place which would have been broken if unregisterWithVirtualBox() failed
1825 * after we successfully deleted the storage) */
1826
1827 rc = unregisterWithVirtualBox();
1828
1829 alock.enter();
1830
1831 /* restore the state because we may fail below; we will set it later again*/
1832 m.state = MediaState_Created;
1833
1834 CheckComRCReturnRC (rc);
1835
1836 ComObjPtr <Progress> progress;
1837
1838 if (aProgress != NULL)
1839 {
1840 /* use the existing progress object... */
1841 progress = *aProgress;
1842
1843 /* ...but create a new one if it is null */
1844 if (progress.isNull())
1845 {
1846 progress.createObject();
1847 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1848 BstrFmt (tr ("Deleting hard disk storage unit '%ls'"),
1849 name().raw()),
1850 FALSE /* aCancelable */);
1851 CheckComRCReturnRC (rc);
1852 }
1853 }
1854
1855 std::auto_ptr <Task> task (new Task (this, progress, Task::Delete));
1856 AssertComRCReturnRC (task->autoCaller.rc());
1857
1858 if (aWait)
1859 {
1860 /* go to Deleting state before starting the task */
1861 m.state = MediaState_Deleting;
1862
1863 rc = task->runNow();
1864 }
1865 else
1866 {
1867 rc = task->startThread();
1868 CheckComRCReturnRC (rc);
1869
1870 /* go to Deleting state before leaving the lock */
1871 m.state = MediaState_Deleting;
1872 }
1873
1874 /* task is now owned (or already deleted) by taskThread() so release it */
1875 task.release();
1876
1877 if (aProgress != NULL)
1878 {
1879 /* return progress to the caller */
1880 *aProgress = progress;
1881 }
1882
1883 return rc;
1884}
1885
1886/**
1887 * Creates a new differencing storage unit using the given target hard disk's
1888 * format and the location. Note that @c aTarget must be NotCreated.
1889 *
1890 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
1891 * this hard disk for reading assuming that the caller has already done so. This
1892 * is used when taking an online snaopshot (where all origial hard disks are
1893 * locked for writing and must remain such). Note however that if @a aWait is
1894 * @c false and this method returns a success then the thread started by
1895 * this method will* unlock the hard disk (unless it is in
1896 * MediaState_LockedWrite state) so make sure the hard disk is either in
1897 * MediaState_LockedWrite or call #LockRead() before calling this method! If @a
1898 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
1899 * make sure you do it yourself as needed.
1900 *
1901 * If @a aProgress is not NULL but the object it points to is @c null then a new
1902 * progress object will be created and assigned to @a *aProgress on success,
1903 * otherwise the existing progress object is used. If Progress is NULL, then no
1904 * progress object is created/used at all.
1905 *
1906 * When @a aWait is @c false, this method will create a thread to perform the
1907 * create operation asynchronously and will return immediately. Otherwise, it
1908 * will perform the operation on the calling thread and will not return to the
1909 * caller until the operation is completed. Note that @a aProgress cannot be
1910 * NULL when @a aWait is @c false (this method will assert in this case).
1911 *
1912 * @param aTarget Target hard disk.
1913 * @param aProgress Where to find/store a Progress object to track operation
1914 * completion.
1915 * @param aWait @c true if this method should block instead of creating
1916 * an asynchronous thread.
1917 *
1918 * @note Locks this object and aTarget for writing.
1919 */
1920HRESULT HardDisk2::createDiffStorage (ComObjPtr <HardDisk2> &aTarget,
1921 ComObjPtr <Progress> *aProgress,
1922 bool aWait)
1923{
1924 AssertReturn (!aTarget.isNull(), E_FAIL);
1925 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
1926
1927 AutoCaller autoCaller (this);
1928 CheckComRCReturnRC (autoCaller.rc());
1929
1930 AutoCaller targetCaller (aTarget);
1931 CheckComRCReturnRC (targetCaller.rc());
1932
1933 AutoMultiWriteLock2 alock (this, aTarget);
1934
1935 AssertReturn (mm.type != HardDiskType_Writethrough, E_FAIL);
1936
1937 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
1938 AssertReturn (m.state == MediaState_LockedRead ||
1939 m.state == MediaState_LockedWrite, E_FAIL);
1940
1941 if (aTarget->m.state != MediaState_NotCreated)
1942 return aTarget->setStateError();
1943
1944 HRESULT rc = S_OK;
1945
1946 /* check that the hard disk is not attached to any VM in the current state*/
1947 for (BackRefList::const_iterator it = m.backRefs.begin();
1948 it != m.backRefs.end(); ++ it)
1949 {
1950 if (it->inCurState)
1951 {
1952 /* Note: when a VM snapshot is being taken, all normal hard disks
1953 * attached to the VM in the current state will be, as an exception,
1954 * also associated with the snapshot which is about to create (see
1955 * SnapshotMachine::init()) before deassociating them from the
1956 * current state (which takes place only on success in
1957 * Machine::fixupHardDisks2()), so that the size of snapshotIds
1958 * will be 1 in this case. The given condition is used to filter out
1959 * this legal situatinon and do not report an error. */
1960
1961 if (it->snapshotIds.size() == 0)
1962 {
1963 return setError (E_FAIL,
1964 tr ("Hard disk '%ls' is attached to a virtual machine "
1965 "with UUID {%RTuuid}. No differencing hard disks "
1966 "based on it may be created until it is detached"),
1967 m.location.raw(), it->machineId.raw());
1968 }
1969
1970 Assert (it->snapshotIds.size() == 1);
1971 }
1972 }
1973
1974 ComObjPtr <Progress> progress;
1975
1976 if (aProgress != NULL)
1977 {
1978 /* use the existing progress object... */
1979 progress = *aProgress;
1980
1981 /* ...but create a new one if it is null */
1982 if (progress.isNull())
1983 {
1984 progress.createObject();
1985 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
1986 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
1987 aTarget->name().raw()),
1988 FALSE /* aCancelable */);
1989 CheckComRCReturnRC (rc);
1990 }
1991 }
1992
1993 /* setup task object and thread to carry out the operation
1994 * asynchronously */
1995
1996 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
1997 AssertComRCReturnRC (task->autoCaller.rc());
1998
1999 task->setData (aTarget);
2000
2001 /* register a task (it will deregister itself when done) */
2002 ++ mm.numCreateDiffTasks;
2003 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
2004
2005 if (aWait)
2006 {
2007 /* go to Creating state before starting the task */
2008 aTarget->m.state = MediaState_Creating;
2009
2010 rc = task->runNow();
2011 }
2012 else
2013 {
2014 rc = task->startThread();
2015 CheckComRCReturnRC (rc);
2016
2017 /* go to Creating state before leaving the lock */
2018 aTarget->m.state = MediaState_Creating;
2019 }
2020
2021 /* task is now owned (or already deleted) by taskThread() so release it */
2022 task.release();
2023
2024 if (aProgress != NULL)
2025 {
2026 /* return progress to the caller */
2027 *aProgress = progress;
2028 }
2029
2030 return rc;
2031}
2032
2033/**
2034 * Prepares this (source) hard disk, target hard disk and all intermediate hard
2035 * disks for the merge operation.
2036 *
2037 * This method is to be called prior to calling the #mergeTo() to perform
2038 * necessary consistency checks and place involved hard disks to appropriate
2039 * states. If #mergeTo() is not called or fails, the state modifications
2040 * performed by this method must be undone by #cancelMergeTo().
2041 *
2042 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
2043 * responsibility to detach the source and all intermediate hard disks before
2044 * calling #mergeTo() (which will fail otherwise).
2045 *
2046 * See #mergeTo() for more information about merging.
2047 *
2048 * @param aTarget Target hard disk.
2049 * @param aChain Where to store the created merge chain.
2050 * @param aIgnoreAttachments Don't check if the source or any intermediate
2051 * hard disk is attached to any VM.
2052 *
2053 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2054 * intermediate hard disks for writing.
2055 */
2056HRESULT HardDisk2::prepareMergeTo (HardDisk2 *aTarget,
2057 MergeChain * &aChain,
2058 bool aIgnoreAttachments /*= false*/)
2059{
2060 AssertReturn (aTarget != NULL, E_FAIL);
2061
2062 AutoCaller autoCaller (this);
2063 AssertComRCReturnRC (autoCaller.rc());
2064
2065 AutoCaller targetCaller (aTarget);
2066 AssertComRCReturnRC (targetCaller.rc());
2067
2068 aChain = NULL;
2069
2070 /* we walk the tree */
2071 AutoReadLock treeLock (this->treeLock());
2072
2073 HRESULT rc = S_OK;
2074
2075 /* detect the merge direction */
2076 bool forward;
2077 {
2078 HardDisk2 *parent = mParent;
2079 while (parent != NULL && parent != aTarget)
2080 parent = parent->mParent;
2081 if (parent == aTarget)
2082 forward = false;
2083 else
2084 {
2085 parent = aTarget->mParent;
2086 while (parent != NULL && parent != this)
2087 parent = parent->mParent;
2088 if (parent == this)
2089 forward = true;
2090 else
2091 {
2092 Bstr tgtLoc;
2093 {
2094 AutoReadLock alock (this);
2095 tgtLoc = aTarget->locationFull();
2096 }
2097
2098 AutoReadLock alock (this);
2099 return setError (E_FAIL,
2100 tr ("Hard disks '%ls' and '%ls' are unrelated"),
2101 m.locationFull.raw(), tgtLoc.raw());
2102 }
2103 }
2104 }
2105
2106 /* build the chain (will do necessary checks and state changes) */
2107 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2108 aIgnoreAttachments));
2109 {
2110 HardDisk2 *last = forward ? aTarget : this;
2111 HardDisk2 *first = forward ? this : aTarget;
2112
2113 for (;;)
2114 {
2115 if (last == aTarget)
2116 rc = chain->addTarget (last);
2117 else if (last == this)
2118 rc = chain->addSource (last);
2119 else
2120 rc = chain->addIntermediate (last);
2121 CheckComRCReturnRC (rc);
2122
2123 if (last == first)
2124 break;
2125
2126 last = last->mParent;
2127 }
2128 }
2129
2130 aChain = chain.release();
2131
2132 return S_OK;
2133}
2134
2135/**
2136 * Merges this hard disk to the specified hard disk which must be either its
2137 * direct ancestor or descendant.
2138 *
2139 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2140 * get two varians of the merge operation:
2141 *
2142 * forward merge
2143 * ------------------------->
2144 * [Extra] <- SOURCE <- Intermediate <- TARGET
2145 * Any Del Del LockWr
2146 *
2147 *
2148 * backward merge
2149 * <-------------------------
2150 * TARGET <- Intermediate <- SOURCE <- [Extra]
2151 * LockWr Del Del LockWr
2152 *
2153 * Each scheme shows the involved hard disks on the hard disk chain where
2154 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2155 * the hard disk must have at a time of the mergeTo() call.
2156 *
2157 * The hard disks in the square braces may be absent (e.g. when the forward
2158 * operation takes place and SOURCE is the base hard disk, or when the backward
2159 * merge operation takes place and TARGET is the last child in the chain) but if
2160 * they present they are involved too as shown.
2161 *
2162 * Nor the source hard disk neither intermediate hard disks may be attached to
2163 * any VM directly or in the snapshot, otherwise this method will assert.
2164 *
2165 * The #prepareMergeTo() method must be called prior to this method to place all
2166 * involved to necessary states and perform other consistency checks.
2167 *
2168 * If @a aWait is @c true then this method will perform the operation on the
2169 * calling thread and will not return to the caller until the operation is
2170 * completed. When this method succeeds, all intermediate hard disk objects in
2171 * the chain will be uninitialized, the state of the target hard disk (and all
2172 * involved extra hard disks) will be restored and @a aChain will be deleted.
2173 * Note that this (source) hard disk is not uninitialized because of possible
2174 * AutoCaller instances held by the caller of this method on the current thread.
2175 * It's therefore the responsibility of the caller to call HardDisk2::uninit()
2176 * after releasing all callers in this case!
2177 *
2178 * If @a aWait is @c false then this method will crea,te a thread to perform the
2179 * create operation asynchronously and will return immediately. If the operation
2180 * succeeds, the thread will uninitialize the source hard disk object and all
2181 * intermediate hard disk objects in the chain, reset the state of the target
2182 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2183 * operation fails, the thread will only reset the states of all involved hard
2184 * disks and delete @a aChain.
2185 *
2186 * When this method fails (regardless of the @a aWait mode), it is a caller's
2187 * responsiblity to undo state changes and delete @a aChain using
2188 * #cancelMergeTo().
2189 *
2190 * If @a aProgress is not NULL but the object it points to is @c null then a new
2191 * progress object will be created and assigned to @a *aProgress on success,
2192 * otherwise the existing progress object is used. If Progress is NULL, then no
2193 * progress object is created/used at all. Note that @a aProgress cannot be
2194 * NULL when @a aWait is @c false (this method will assert in this case).
2195 *
2196 * @param aChain Merge chain created by #prepareMergeTo().
2197 * @param aProgress Where to find/store a Progress object to track operation
2198 * completion.
2199 * @param aWait @c true if this method should block instead of creating
2200 * an asynchronous thread.
2201 *
2202 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2203 * for writing.
2204 */
2205HRESULT HardDisk2::mergeTo (MergeChain *aChain,
2206 ComObjPtr <Progress> *aProgress,
2207 bool aWait)
2208{
2209 AssertReturn (aChain != NULL, E_FAIL);
2210 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2211
2212 AutoCaller autoCaller (this);
2213 CheckComRCReturnRC (autoCaller.rc());
2214
2215 HRESULT rc = S_OK;
2216
2217 ComObjPtr <Progress> progress;
2218
2219 if (aProgress != NULL)
2220 {
2221 /* use the existing progress object... */
2222 progress = *aProgress;
2223
2224 /* ...but create a new one if it is null */
2225 if (progress.isNull())
2226 {
2227 AutoReadLock alock (this);
2228
2229 progress.createObject();
2230 rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
2231 BstrFmt (tr ("Merging hard disk '%ls' to '%ls'"),
2232 name().raw(), aChain->target()->name().raw()),
2233 FALSE /* aCancelable */);
2234 CheckComRCReturnRC (rc);
2235 }
2236 }
2237
2238 /* setup task object and thread to carry out the operation
2239 * asynchronously */
2240
2241 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2242 AssertComRCReturnRC (task->autoCaller.rc());
2243
2244 task->setData (aChain);
2245
2246 /* Note: task owns aChain (will delete it when not needed) in all cases
2247 * except when @a aWait is @c true and runNow() fails -- in this case
2248 * aChain will be left away because cancelMergeTo() will be applied by the
2249 * caller on it as it is required in the documentation above */
2250
2251 if (aWait)
2252 {
2253 rc = task->runNow();
2254 }
2255 else
2256 {
2257 rc = task->startThread();
2258 CheckComRCReturnRC (rc);
2259 }
2260
2261 /* task is now owned (or already deleted) by taskThread() so release it */
2262 task.release();
2263
2264 if (aProgress != NULL)
2265 {
2266 /* return progress to the caller */
2267 *aProgress = progress;
2268 }
2269
2270 return rc;
2271}
2272
2273/**
2274 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2275 * or fails. Frees memory occupied by @a aChain.
2276 *
2277 * @param aChain Merge chain created by #prepareMergeTo().
2278 *
2279 * @note Locks the hard disks from the chain for writing.
2280 */
2281void HardDisk2::cancelMergeTo (MergeChain *aChain)
2282{
2283 AutoCaller autoCaller (this);
2284 AssertComRCReturnVoid (autoCaller.rc());
2285
2286 AssertReturnVoid (aChain != NULL);
2287
2288 /* the destructor will do the thing */
2289 delete aChain;
2290}
2291
2292// private methods
2293////////////////////////////////////////////////////////////////////////////////
2294
2295/**
2296 * Sets the value of m.location and calculates the value of m.locationFull.
2297 *
2298 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2299 * locations and to prepend the default hard disk folder if the given location
2300 * string does not contain any path information at all.
2301 *
2302 * Also, if the specified location is a file path that ends with '/' then the
2303 * file name part will be generated by this method automatically in the format
2304 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2305 * and assign to this medium, and <ext> is the default extension for this
2306 * medium's storage format. Note that this procedure requires the media state to
2307 * be NotCreated and will return a faiulre otherwise.
2308 *
2309 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2310 * then it can be relative to the VirtualBox home directory.
2311 *
2312 * @note Must be called from under this object's write lock.
2313 */
2314HRESULT HardDisk2::setLocation (const BSTR aLocation)
2315{
2316 if (aLocation == NULL)
2317 return E_INVALIDARG;
2318
2319 AutoCaller autoCaller (this);
2320 AssertComRCReturnRC (autoCaller.rc());
2321
2322 /* formatObj may be null only when initializing from an existing path and
2323 * no format is known yet */
2324 AssertReturn ((!mm.format.isNull() && !mm.formatObj.isNull()) ||
2325 (autoCaller.state() == InInit &&
2326 m.state != MediaState_NotCreated && m.id.isEmpty() &&
2327 mm.format.isNull() && mm.formatObj.isNull()),
2328 E_FAIL);
2329
2330 /* are we dealing with a hard disk opened from the existing path? */
2331 bool isNew = mm.format.isNull();
2332
2333 if (isNew ||
2334 (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File))
2335 {
2336 Guid id;
2337
2338 Utf8Str location (aLocation);
2339
2340 if (m.state == MediaState_NotCreated)
2341 {
2342 /* must be a file (formatObj must be already known) */
2343 Assert (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File);
2344
2345 if (RTPathFilename (location) == NULL)
2346 {
2347 /* no file name is given (either an empty string or ends with a
2348 * slash), generate a new UUID + file name if the state allows
2349 * this */
2350
2351 ComAssertMsgRet (!mm.formatObj->fileExtensions().empty(),
2352 ("Must be at least one extension if it is "
2353 "HardDiskFormatCapabilities_File\n"),
2354 E_FAIL);
2355
2356 Bstr ext = mm.formatObj->fileExtensions().front();
2357 ComAssertMsgRet (!ext.isEmpty(),
2358 ("Default extension must not be empty\n"),
2359 E_FAIL);
2360
2361 id.create();
2362
2363 location = Utf8StrFmt ("%s{%RTuuid}.%ls",
2364 location.raw(), id.raw(), ext.raw());
2365 }
2366 }
2367
2368 /* append the default folder if no path is given */
2369 if (!RTPathHavePath (location))
2370 {
2371 AutoReadLock propsLock (mVirtualBox->systemProperties());
2372 location = Utf8StrFmt ("%ls%c%s",
2373 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2374 RTPATH_DELIMITER,
2375 location.raw());
2376 }
2377
2378 /* get the full file name */
2379 Utf8Str locationFull;
2380 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
2381 if (RT_FAILURE (vrc))
2382 return setError (E_FAIL,
2383 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2384 location.raw(), vrc);
2385
2386 /* detect the backend from the storage unit if new */
2387 if (isNew)
2388 {
2389 char *backendName = NULL;
2390
2391 /* is it a file? */
2392 {
2393 RTFILE file;
2394 vrc = RTFileOpen (&file, locationFull, RTFILE_O_READ);
2395 if (RT_SUCCESS (vrc))
2396 RTFileClose (file);
2397 }
2398 if (RT_SUCCESS (vrc))
2399 {
2400 vrc = VDGetFormat (locationFull, &backendName);
2401 }
2402 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2403 {
2404 /* assume it's not a file, restore the original location */
2405 location = locationFull = aLocation;
2406 vrc = VDGetFormat (locationFull, &backendName);
2407 }
2408
2409 if (RT_FAILURE (vrc))
2410 return setError (E_FAIL,
2411 tr ("Could not get the storage format of the hard disk "
2412 "'%s' (%Rrc)"), locationFull.raw(), vrc);
2413
2414 ComAssertRet (backendName != NULL && *backendName != '\0', E_FAIL);
2415
2416 HRESULT rc = setFormat (Bstr (backendName));
2417 RTStrFree (backendName);
2418
2419 /* setFormat() must not fail since we've just used the backend so
2420 * the format object must be there */
2421 AssertComRCReturnRC (rc);
2422 }
2423
2424 /* is it still a file? */
2425 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
2426 {
2427 m.location = location;
2428 m.locationFull = locationFull;
2429
2430 /* assign a new UUID if we generated it */
2431 if (!id.isEmpty())
2432 unconst (m.id) = id;
2433 }
2434 else
2435 {
2436 m.location = locationFull;
2437 m.locationFull = locationFull;
2438 }
2439 }
2440 else
2441 {
2442 m.location = aLocation;
2443 m.locationFull = aLocation;
2444 }
2445
2446 return S_OK;
2447}
2448
2449/**
2450 * Checks that the format ID is valid and sets it on success.
2451 *
2452 * Note that this method will caller-reference the format object on success!
2453 * This reference must be released somewhere to let the HardDiskFormat object be
2454 * uninitialized.
2455 *
2456 * @note Must be called from under this object's write lock.
2457 */
2458HRESULT HardDisk2::setFormat (const BSTR aFormat)
2459{
2460 /* get the format object first */
2461 {
2462 AutoReadLock propsLock (mVirtualBox->systemProperties());
2463
2464 unconst (mm.formatObj)
2465 = mVirtualBox->systemProperties()->hardDiskFormat (aFormat);
2466 if (mm.formatObj.isNull())
2467 return setError (E_FAIL,
2468 tr ("Invalid hard disk storage format '%ls'"), aFormat);
2469
2470 /* reference the format permanently to prevent its unexpected
2471 * uninitialization */
2472 HRESULT rc = mm.formatObj->addCaller();
2473 AssertComRCReturnRC (rc);
2474
2475 /* get properties (preinsert them as keys in the map). Note that the
2476 * map doesn't grow over the object life time since the set of
2477 * properties is meant to be constant. */
2478
2479 mm.properties.clear();
2480
2481 for (HardDiskFormat::PropertyList::const_iterator it =
2482 mm.formatObj->properties().begin();
2483 it != mm.formatObj->properties().end();
2484 ++ it)
2485 {
2486 mm.properties.insert (std::make_pair (it->name, Bstr::Null));
2487 }
2488 }
2489
2490 unconst (mm.format) = aFormat;
2491
2492 return S_OK;
2493}
2494
2495/**
2496 * Queries information from the image file.
2497 *
2498 * As a result of this call, the accessibility state and data members such as
2499 * size and description will be updated with the current information.
2500 *
2501 * Reimplements MediumBase::queryInfo() to query hard disk information using the
2502 * VD backend interface.
2503 *
2504 * @note This method may block during a system I/O call that checks storage
2505 * accessibility.
2506 *
2507 * @note Locks treeLock() for reading and writing (for new diff media checked
2508 * for the first time). Locks mParent for reading. Locks this object for
2509 * writing.
2510 */
2511HRESULT HardDisk2::queryInfo()
2512{
2513 AutoWriteLock alock (this);
2514
2515 AssertReturn (m.state == MediaState_Created ||
2516 m.state == MediaState_Inaccessible ||
2517 m.state == MediaState_LockedRead ||
2518 m.state == MediaState_LockedWrite,
2519 E_FAIL);
2520
2521 HRESULT rc = S_OK;
2522
2523 int vrc = VINF_SUCCESS;
2524
2525 /* check if a blocking queryInfo() call is in progress on some other thread,
2526 * and wait for it to finish if so instead of querying data ourselves */
2527 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
2528 {
2529 Assert (m.state == MediaState_LockedRead);
2530
2531 ++ m.queryInfoCallers;
2532 alock.leave();
2533
2534 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
2535
2536 alock.enter();
2537 -- m.queryInfoCallers;
2538
2539 if (m.queryInfoCallers == 0)
2540 {
2541 /* last waiting caller deletes the semaphore */
2542 RTSemEventMultiDestroy (m.queryInfoSem);
2543 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2544 }
2545
2546 AssertRC (vrc);
2547
2548 return S_OK;
2549 }
2550
2551 /* lazily create a semaphore for possible callers */
2552 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
2553 ComAssertRCRet (vrc, E_FAIL);
2554
2555 bool tempStateSet = false;
2556 if (m.state != MediaState_LockedRead &&
2557 m.state != MediaState_LockedWrite)
2558 {
2559 /* Cause other methods to prevent any modifications before leaving the
2560 * lock. Note that clients will never see this temporary state change
2561 * since any COMGETTER(State) is (or will be) blocked until we finish
2562 * and restore the actual state. */
2563 m.state = MediaState_LockedRead;
2564 tempStateSet = true;
2565 }
2566
2567 /* leave the lock before a blocking operation */
2568 alock.leave();
2569
2570 bool success = false;
2571 Utf8Str lastAccessError;
2572
2573 try
2574 {
2575 Utf8Str location (m.locationFull);
2576
2577 /* are we dealing with a hard disk opened from the existing path? */
2578 bool isNew = m.id.isEmpty();
2579
2580 PVBOXHDD hdd;
2581 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
2582 ComAssertRCThrow (vrc, E_FAIL);
2583
2584 try
2585 {
2586 unsigned flags = VD_OPEN_FLAGS_INFO;
2587
2588 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
2589 * hard disks because that would prevent necessary modifications
2590 * when opening hard disks of some third-party formats for the first
2591 * time in VirtualBox (such as VMDK for which VDOpen() needs to
2592 * generate an UUID if it is missing) */
2593 if (!isNew)
2594 flags |= VD_OPEN_FLAGS_READONLY;
2595
2596 vrc = VDOpen (hdd, Utf8Str (mm.format), location, flags, NULL);
2597 if (RT_FAILURE (vrc))
2598 {
2599 lastAccessError = Utf8StrFmt (
2600 tr ("Could not open the hard disk '%ls'%s"),
2601 m.locationFull.raw(), vdError (vrc).raw());
2602 throw S_OK;
2603 }
2604
2605 /* check the UUID */
2606 RTUUID uuid;
2607 vrc = VDGetUuid (hdd, 0, &uuid);
2608 ComAssertRCThrow (vrc, E_FAIL);
2609
2610 if (isNew)
2611 {
2612 unconst (m.id) = uuid;
2613 }
2614 else
2615 {
2616 if (m.id != uuid)
2617 {
2618 lastAccessError = Utf8StrFmt (
2619 tr ("UUID {%RTuuid} of the hard disk '%ls' "
2620 "does not match the value {%RTuuid} stored in the "
2621 "media registry ('%ls')"),
2622 &uuid, m.locationFull.raw(), m.id.raw(),
2623 mVirtualBox->settingsFileName().raw());
2624 throw S_OK;
2625 }
2626 }
2627
2628 /* check the type */
2629 VDIMAGETYPE type;
2630 vrc = VDGetImageType (hdd, 0, &type);
2631 ComAssertRCThrow (vrc, E_FAIL);
2632
2633 if (type == VD_IMAGE_TYPE_DIFF)
2634 {
2635 vrc = VDGetParentUuid (hdd, 0, &uuid);
2636 ComAssertRCThrow (vrc, E_FAIL);
2637
2638 if (isNew)
2639 {
2640 /* the parent must be known to us. Note that we freely
2641 * call locking methods of mVirtualBox and parent from the
2642 * write lock (breaking the {parent,child} lock order)
2643 * because there may be no concurrent access to the just
2644 * opened hard disk on ther threads yet (and init() will
2645 * fail if this method reporst MediaState_Inaccessible) */
2646
2647 Guid id = uuid;
2648 ComObjPtr <HardDisk2> parent;
2649 rc = mVirtualBox->findHardDisk2 (&id, NULL,
2650 false /* aSetError */,
2651 &parent);
2652 if (FAILED (rc))
2653 {
2654 lastAccessError = Utf8StrFmt (
2655 tr ("Parent hard disk with UUID {%RTuuid} of the "
2656 "hard disk '%ls' is not found in the media "
2657 "registry ('%ls')"),
2658 &uuid, m.locationFull.raw(),
2659 mVirtualBox->settingsFileName().raw());
2660 throw S_OK;
2661 }
2662
2663 /* deassociate from VirtualBox, associate with parent */
2664
2665 mVirtualBox->removeDependentChild (this);
2666
2667 /* we set mParent & children() */
2668 AutoWriteLock treeLock (this->treeLock());
2669
2670 Assert (mParent.isNull());
2671 mParent = parent;
2672 mParent->addDependentChild (this);
2673 }
2674 else
2675 {
2676 /* we access mParent */
2677 AutoReadLock treeLock (this->treeLock());
2678
2679 /* check that parent UUIDs match. Note that there's no need
2680 * for the parent's AutoCaller (our lifetime is bound to
2681 * it) */
2682
2683 if (mParent.isNull())
2684 {
2685 lastAccessError = Utf8StrFmt (
2686 tr ("Hard disk '%ls' is differencing but it is not "
2687 "associated with any parent hard disk in the "
2688 "media registry ('%ls')"),
2689 m.locationFull.raw(),
2690 mVirtualBox->settingsFileName().raw());
2691 throw S_OK;
2692 }
2693
2694 AutoReadLock parentLock (mParent);
2695 if (mParent->state() != MediaState_Inaccessible &&
2696 mParent->id() != uuid)
2697 {
2698 lastAccessError = Utf8StrFmt (
2699 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' "
2700 "does not match UUID {%RTuuid} of its parent "
2701 "hard disk stored in the media registry ('%ls')"),
2702 &uuid, m.locationFull.raw(),
2703 mParent->id().raw(),
2704 mVirtualBox->settingsFileName().raw());
2705 throw S_OK;
2706 }
2707
2708 /// @todo NEWMEDIA what to do if the parent is not
2709 /// accessible while the diff is? Probably, nothing. The
2710 /// real code will detect the mismatch anyway.
2711 }
2712 }
2713
2714 m.size = VDGetFileSize (hdd, 0);
2715 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
2716
2717 success = true;
2718 }
2719 catch (HRESULT aRC)
2720 {
2721 rc = aRC;
2722 }
2723
2724 VDDestroy (hdd);
2725
2726 }
2727 catch (HRESULT aRC)
2728 {
2729 rc = aRC;
2730 }
2731
2732 alock.enter();
2733
2734 /* inform other callers if there are any */
2735 if (m.queryInfoCallers > 0)
2736 {
2737 RTSemEventMultiSignal (m.queryInfoSem);
2738 }
2739 else
2740 {
2741 /* delete the semaphore ourselves */
2742 RTSemEventMultiDestroy (m.queryInfoSem);
2743 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2744 }
2745
2746 /* Restore the proper state when appropriate. Keep in mind that LockedRead
2747 * and LockedWrite are not transitable to Inaccessible. */
2748 if (success)
2749 {
2750 if (tempStateSet)
2751 m.state = MediaState_Created;
2752 m.lastAccessError.setNull();
2753 }
2754 else
2755 {
2756 if (tempStateSet)
2757 m.state = MediaState_Inaccessible;
2758 m.lastAccessError = lastAccessError;
2759
2760 LogWarningFunc (("'%ls' is not accessible (error='%ls', "
2761 "rc=%Rhrc, vrc=%Rrc)\n",
2762 m.locationFull.raw(), m.lastAccessError.raw(),
2763 rc, vrc));
2764 }
2765
2766 return rc;
2767}
2768
2769/**
2770 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
2771 * write lock.
2772 *
2773 * @note Locks treeLock() for reading.
2774 */
2775HRESULT HardDisk2::canClose()
2776{
2777 /* we access children */
2778 AutoReadLock treeLock (this->treeLock());
2779
2780 if (children().size() != 0)
2781 return setError (E_FAIL,
2782 tr ("Hard disk '%ls' has %d child hard disks"),
2783 children().size());
2784
2785 return S_OK;
2786}
2787
2788/**
2789 * @note Called from within this object's AutoWriteLock.
2790 */
2791HRESULT HardDisk2::canAttach (const Guid &aMachineId,
2792 const Guid &aSnapshotId)
2793{
2794 if (mm.numCreateDiffTasks > 0)
2795 return setError (E_FAIL,
2796 tr ("One or more differencing child hard disks are "
2797 "being created for the hard disk '%ls' (%u)"),
2798 m.locationFull.raw(), mm.numCreateDiffTasks);
2799
2800 return S_OK;
2801}
2802
2803/**
2804 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
2805 * from under mVirtualBox write lock.
2806 *
2807 * @note Locks treeLock() for writing.
2808 */
2809HRESULT HardDisk2::unregisterWithVirtualBox()
2810{
2811 /* Note that we need to de-associate ourselves from the parent to let
2812 * unregisterHardDisk2() properly save the registry */
2813
2814 /* we modify mParent and access children */
2815 AutoWriteLock treeLock (this->treeLock());
2816
2817 const ComObjPtr <HardDisk2, ComWeakRef> parent = mParent;
2818
2819 AssertReturn (children().size() == 0, E_FAIL);
2820
2821 if (!mParent.isNull())
2822 {
2823 /* deassociate from the parent, associate with VirtualBox */
2824 mVirtualBox->addDependentChild (this);
2825 mParent->removeDependentChild (this);
2826 mParent.setNull();
2827 }
2828
2829 HRESULT rc = mVirtualBox->unregisterHardDisk2 (this);
2830
2831 if (FAILED (rc))
2832 {
2833 if (!parent.isNull())
2834 {
2835 /* re-associate with the parent as we are still relatives in the
2836 * registry */
2837 mParent = parent;
2838 mParent->addDependentChild (this);
2839 mVirtualBox->removeDependentChild (this);
2840 }
2841 }
2842
2843 return rc;
2844}
2845
2846/**
2847 * Returns the last error message collected by the vdErrorCall callback and
2848 * resets it.
2849 *
2850 * The error message is returned prepended with a dot and a space, like this:
2851 * <code>
2852 * ". <error_text> (%Rrc)"
2853 * </code>
2854 * to make it easily appendable to a more general error message. The @c %Rrc
2855 * format string is given @a aVRC as an argument.
2856 *
2857 * If there is no last error message collected by vdErrorCall or if it is a
2858 * null or empty string, then this function returns the following text:
2859 * <code>
2860 * " (%Rrc)"
2861 * </code>
2862 *
2863 * @note Doesn't do any object locking; it is assumed that the caller makes sure
2864 * the callback isn't called by more than one thread at a time.
2865 *
2866 * @param aVRC VBox error code to use when no error message is provided.
2867 */
2868Utf8Str HardDisk2::vdError (int aVRC)
2869{
2870 Utf8Str error;
2871
2872 if (mm.vdError.isEmpty())
2873 error = Utf8StrFmt (" (%Rrc)", aVRC);
2874 else
2875 error = Utf8StrFmt (". %s (%Rrc)", mm.vdError.raw(), aVRC);
2876
2877 mm.vdError.setNull();
2878
2879 return error;
2880}
2881
2882/**
2883 * Error message callback.
2884 *
2885 * Puts the reported error message to the mm.vdError field.
2886 *
2887 * @note Doesn't do any object locking; it is assumed that the caller makes sure
2888 * the callback isn't called by more than one thread at a time.
2889 *
2890 * @param pvUser The opaque data passed on container creation.
2891 * @param rc The VBox error code.
2892 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
2893 * @param pszFormat Error message format string.
2894 * @param va Error message arguments.
2895 */
2896/*static*/
2897DECLCALLBACK(void) HardDisk2::vdErrorCall (void *pvUser, int rc, RT_SRC_POS_DECL,
2898 const char *pszFormat, va_list va)
2899{
2900 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
2901 AssertReturnVoid (that != NULL);
2902
2903 that->mm.vdError = Utf8StrFmtVA (pszFormat, va);
2904}
2905
2906/**
2907 * PFNVMPROGRESS callback handler for Task operations.
2908 *
2909 * @param uPercent Completetion precentage (0-100).
2910 * @param pvUser Pointer to the Progress instance.
2911 */
2912/*static*/
2913DECLCALLBACK(int) HardDisk2::vdProgressCall (PVM /* pVM */, unsigned uPercent,
2914 void *pvUser)
2915{
2916 HardDisk2 *that = static_cast <HardDisk2 *> (pvUser);
2917 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
2918
2919 if (that->mm.vdProgress != NULL)
2920 {
2921 /* update the progress object, capping it at 99% as the final percent
2922 * is used for additional operations like setting the UUIDs and similar. */
2923 that->mm.vdProgress->notifyProgress (RT_MIN (uPercent, 99));
2924 }
2925
2926 return VINF_SUCCESS;
2927}
2928
2929/**
2930 * Thread function for time-consuming tasks.
2931 *
2932 * The Task structure passed to @a pvUser must be allocated using new and will
2933 * be freed by this method before it returns.
2934 *
2935 * @param pvUser Pointer to the Task instance.
2936 */
2937/* static */
2938DECLCALLBACK(int) HardDisk2::taskThread (RTTHREAD thread, void *pvUser)
2939{
2940 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
2941 AssertReturn (task.get(), VERR_GENERAL_FAILURE);
2942
2943 bool isAsync = thread != NIL_RTTHREAD;
2944
2945 HardDisk2 *that = task->that;
2946
2947 /// @todo ugly hack, fix ComAssert... later
2948 #define setError that->setError
2949
2950 /* Note: no need in AutoCaller because Task does that */
2951
2952 LogFlowFuncEnter();
2953 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
2954
2955 HRESULT rc = S_OK;
2956
2957 switch (task->operation)
2958 {
2959 ////////////////////////////////////////////////////////////////////////
2960
2961 case Task::CreateDynamic:
2962 case Task::CreateFixed:
2963 {
2964 /* The lock is also used as a signal from the task initiator (which
2965 * releases it only after RTThreadCreate()) that we can start the job */
2966 AutoWriteLock thatLock (that);
2967
2968 /* these parameters we need after creation */
2969 RTUUID uuid;
2970 uint64_t size = 0, logicalSize = 0;
2971
2972 try
2973 {
2974 PVBOXHDD hdd;
2975 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
2976 ComAssertRCThrow (vrc, E_FAIL);
2977
2978 Utf8Str format (that->mm.format);
2979 Utf8Str location (that->m.locationFull);
2980
2981 /* unlock before the potentially lengthy operation */
2982 Assert (that->m.state == MediaState_Creating);
2983 thatLock.leave();
2984
2985 try
2986 {
2987 /* ensure the directory exists */
2988 rc = VirtualBox::ensureFilePathExists (location);
2989 CheckComRCThrowRC (rc);
2990
2991 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
2992
2993 /* needed for vdProgressCallback */
2994 that->mm.vdProgress = task->progress;
2995
2996 vrc = VDCreateBase (hdd, format, location,
2997 task->operation == Task::CreateDynamic ?
2998 VD_IMAGE_TYPE_NORMAL :
2999 VD_IMAGE_TYPE_FIXED,
3000 task->d.size * _1M,
3001 VD_IMAGE_FLAGS_NONE,
3002 NULL, &geo, &geo, NULL,
3003 VD_OPEN_FLAGS_NORMAL,
3004 NULL, that->mm.vdDiskIfaces);
3005
3006 if (RT_FAILURE (vrc))
3007 {
3008 throw setError (E_FAIL,
3009 tr ("Could not create the hard disk storage "
3010 "unit '%s'%s"),
3011 location.raw(), that->vdError (vrc).raw());
3012 }
3013
3014 vrc = VDGetUuid (hdd, 0, &uuid);
3015 ComAssertRCThrow (vrc, E_FAIL);
3016
3017 size = VDGetFileSize (hdd, 0);
3018 logicalSize = VDGetSize (hdd, 0) / _1M;
3019 }
3020 catch (HRESULT aRC) { rc = aRC; }
3021
3022 VDDestroy (hdd);
3023 }
3024 catch (HRESULT aRC) { rc = aRC; }
3025
3026 if (SUCCEEDED (rc))
3027 {
3028 /* mVirtualBox->registerHardDisk2() needs a write lock */
3029 AutoWriteLock vboxLock (that->mVirtualBox);
3030 thatLock.enter();
3031
3032 unconst (that->m.id) = uuid;
3033
3034 that->m.size = size;
3035 that->mm.logicalSize = logicalSize;
3036
3037 /* register with mVirtualBox as the last step and move to
3038 * Created state only on success (leaving an orphan file is
3039 * better than breaking media registry consistency) */
3040 rc = that->mVirtualBox->registerHardDisk2 (that);
3041
3042 if (SUCCEEDED (rc))
3043 that->m.state = MediaState_Created;
3044 }
3045
3046 if (FAILED (rc))
3047 {
3048 thatLock.maybeEnter();
3049
3050 /* back to NotCreated on failiure */
3051 that->m.state = MediaState_NotCreated;
3052 }
3053
3054 break;
3055 }
3056
3057 ////////////////////////////////////////////////////////////////////////
3058
3059 case Task::CreateDiff:
3060 {
3061 ComObjPtr <HardDisk2> &target = task->d.target;
3062
3063 /* Lock both in {parent,child} order. The lock is also used as a
3064 * signal from the task initiator (which releases it only after
3065 * RTThreadCreate()) that we can start the job*/
3066 AutoMultiWriteLock2 thatLock (that, target);
3067
3068 uint64_t size = 0, logicalSize = 0;
3069
3070 try
3071 {
3072 PVBOXHDD hdd;
3073 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3074 ComAssertRCThrow (vrc, E_FAIL);
3075
3076 Utf8Str format (that->mm.format);
3077 Utf8Str location (that->m.locationFull);
3078
3079 Utf8Str targetFormat (target->mm.format);
3080 Utf8Str targetLocation (target->m.locationFull);
3081 Guid targetId = target->m.id;
3082
3083 /* UUID must have been set by setLocation() */
3084 Assert (!targetId.isEmpty());
3085
3086 Assert (target->m.state == MediaState_Creating);
3087
3088 /* Note: MediaState_LockedWrite is ok when taking an online
3089 * snapshot */
3090 Assert (that->m.state == MediaState_LockedRead ||
3091 that->m.state == MediaState_LockedWrite);
3092
3093 /* unlock before the potentially lengthy operation */
3094 thatLock.leave();
3095
3096 try
3097 {
3098 vrc = VDOpen (hdd, format, location,
3099 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3100 NULL);
3101 if (RT_FAILURE (vrc))
3102 {
3103 throw setError (E_FAIL,
3104 tr ("Could not open the hard disk storage "
3105 "unit '%s'%s"),
3106 location.raw(), that->vdError (vrc).raw());
3107 }
3108
3109 /* ensure the target directory exists */
3110 rc = VirtualBox::ensureFilePathExists (targetLocation);
3111 CheckComRCThrowRC (rc);
3112
3113 /* needed for vdProgressCallback */
3114 that->mm.vdProgress = task->progress;
3115
3116 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
3117 VD_IMAGE_FLAGS_NONE,
3118 NULL, targetId.raw(),
3119 VD_OPEN_FLAGS_NORMAL,
3120 NULL, that->mm.vdDiskIfaces);
3121
3122 that->mm.vdProgress = NULL;
3123
3124 if (RT_FAILURE (vrc))
3125 {
3126 throw setError (E_FAIL,
3127 tr ("Could not create the differencing hard disk "
3128 "storage unit '%s'%s"),
3129 targetLocation.raw(), that->vdError (vrc).raw());
3130 }
3131
3132 size = VDGetFileSize (hdd, 0);
3133 logicalSize = VDGetSize (hdd, 0) / _1M;
3134 }
3135 catch (HRESULT aRC) { rc = aRC; }
3136
3137 VDDestroy (hdd);
3138 }
3139 catch (HRESULT aRC) { rc = aRC; }
3140
3141 if (SUCCEEDED (rc))
3142 {
3143 /* we set mParent & children() (note that thatLock is released
3144 * here), but lock VirtualBox first to follow the rule */
3145 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3146 that->treeLock());
3147
3148 Assert (target->mParent.isNull());
3149
3150 /* associate the child with the parent and deassociate from
3151 * VirtualBox */
3152 target->mParent = that;
3153 that->addDependentChild (target);
3154 target->mVirtualBox->removeDependentChild (target);
3155
3156 /* register with mVirtualBox as the last step and move to
3157 * Created state only on success (leaving an orphan file is
3158 * better than breaking media registry consistency) */
3159 rc = that->mVirtualBox->registerHardDisk2 (target);
3160
3161 if (FAILED (rc))
3162 {
3163 /* break the parent association on failure to register */
3164 target->mVirtualBox->addDependentChild (target);
3165 that->removeDependentChild (target);
3166 target->mParent.setNull();
3167 }
3168 }
3169
3170 thatLock.maybeEnter();
3171
3172 if (SUCCEEDED (rc))
3173 {
3174 target->m.state = MediaState_Created;
3175
3176 target->m.size = size;
3177 target->mm.logicalSize = logicalSize;
3178 }
3179 else
3180 {
3181 /* back to NotCreated on failiure */
3182 target->m.state = MediaState_NotCreated;
3183 }
3184
3185 if (isAsync)
3186 {
3187 /* unlock ourselves when done (unless in MediaState_LockedWrite
3188 * state because of taking the online snapshot*/
3189 if (that->m.state != MediaState_LockedWrite)
3190 {
3191 HRESULT rc2 = that->UnlockRead (NULL);
3192 AssertComRC (rc2);
3193 }
3194 }
3195
3196 /* deregister the task registered in createDiffStorage() */
3197 Assert (that->mm.numCreateDiffTasks != 0);
3198 -- that->mm.numCreateDiffTasks;
3199
3200 /* Note that in sync mode, it's the caller's responsibility to
3201 * unlock the hard disk */
3202
3203 break;
3204 }
3205
3206 ////////////////////////////////////////////////////////////////////////
3207
3208 case Task::Merge:
3209 {
3210 /* The lock is also used as a signal from the task initiator (which
3211 * releases it only after RTThreadCreate()) that we can start the
3212 * job. We don't actually need the lock for anything else since the
3213 * object is protected by MediaState_Deleting and we don't modify
3214 * its sensitive fields below */
3215 {
3216 AutoWriteLock thatLock (that);
3217 }
3218
3219 MergeChain *chain = task->d.chain.get();
3220
3221#if 1
3222 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
3223#endif
3224
3225 try
3226 {
3227 PVBOXHDD hdd;
3228 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3229 ComAssertRCThrow (vrc, E_FAIL);
3230
3231 try
3232 {
3233 /* open all hard disks in the chain (they are in the
3234 * {parent,child} order in there. Note that we don't lock
3235 * objects in this chain since they must be in states
3236 * (Deleting and LockedWrite) that prevent from chaning
3237 * their format and location fields from outside. */
3238
3239 for (MergeChain::const_iterator it = chain->begin();
3240 it != chain->end(); ++ it)
3241 {
3242 /* complex sanity (sane complexity) */
3243 Assert ((chain->isForward() &&
3244 ((*it != chain->back() &&
3245 (*it)->m.state == MediaState_Deleting) ||
3246 (*it == chain->back() &&
3247 (*it)->m.state == MediaState_LockedWrite))) ||
3248 (!chain->isForward() &&
3249 ((*it != chain->front() &&
3250 (*it)->m.state == MediaState_Deleting) ||
3251 (*it == chain->front() &&
3252 (*it)->m.state == MediaState_LockedWrite))));
3253
3254 Assert (*it == chain->target() ||
3255 (*it)->m.backRefs.size() == 0);
3256
3257 /* open the first image with VDOPEN_FLAGS_INFO because
3258 * it's not necessarily the base one */
3259 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3260 Utf8Str ((*it)->m.locationFull),
3261 it == chain->begin() ?
3262 VD_OPEN_FLAGS_INFO : 0,
3263 NULL);
3264 if (RT_FAILURE (vrc))
3265 throw vrc;
3266#if 1
3267 LogFlow (("*** MERGE disk = %ls\n",
3268 (*it)->m.locationFull.raw()));
3269#endif
3270 }
3271
3272 /* needed for vdProgressCallback */
3273 that->mm.vdProgress = task->progress;
3274
3275 unsigned start = chain->isForward() ?
3276 0 : chain->size() - 1;
3277 unsigned end = chain->isForward() ?
3278 chain->size() - 1 : 0;
3279#if 1
3280 LogFlow (("*** MERGE from %d to %d\n", start, end));
3281#endif
3282 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
3283
3284 that->mm.vdProgress = NULL;
3285
3286 if (RT_FAILURE (vrc))
3287 throw vrc;
3288
3289 /* update parent UUIDs */
3290 /// @todo VDMerge should be taught to do so, including the
3291 /// multiple children case
3292 if (chain->isForward())
3293 {
3294 /* target's UUID needs to be updated (note that target
3295 * is the only image in the container on success) */
3296 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
3297 if (RT_FAILURE (vrc))
3298 throw vrc;
3299 }
3300 else
3301 {
3302 /* we need to update UUIDs of all source's children
3303 * which cannot be part of the container at once so
3304 * add each one in there individually */
3305 if (chain->children().size() > 0)
3306 {
3307 for (List::const_iterator it = chain->children().begin();
3308 it != chain->children().end(); ++ it)
3309 {
3310 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
3311 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3312 Utf8Str ((*it)->m.locationFull),
3313 VD_OPEN_FLAGS_INFO, NULL);
3314 if (RT_FAILURE (vrc))
3315 throw vrc;
3316
3317 vrc = VDSetParentUuid (hdd, 1,
3318 chain->target()->m.id);
3319 if (RT_FAILURE (vrc))
3320 throw vrc;
3321
3322 vrc = VDClose (hdd, false /* fDelete */);
3323 if (RT_FAILURE (vrc))
3324 throw vrc;
3325 }
3326 }
3327 }
3328 }
3329 catch (HRESULT aRC) { rc = aRC; }
3330 catch (int aVRC)
3331 {
3332 throw setError (E_FAIL,
3333 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
3334 chain->source()->m.locationFull.raw(),
3335 chain->target()->m.locationFull.raw(),
3336 that->vdError (aVRC).raw());
3337 }
3338
3339 VDDestroy (hdd);
3340 }
3341 catch (HRESULT aRC) { rc = aRC; }
3342
3343 HRESULT rc2;
3344
3345 bool saveSettingsFailed = false;
3346
3347 if (SUCCEEDED (rc))
3348 {
3349 /* all hard disks but the target were successfully deleted by
3350 * VDMerge; reparent the last one and uninitialize deleted */
3351
3352 /* we set mParent & children() (note that thatLock is released
3353 * here), but lock VirtualBox first to follow the rule */
3354 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3355 that->treeLock());
3356
3357 HardDisk2 *source = chain->source();
3358 HardDisk2 *target = chain->target();
3359
3360 if (chain->isForward())
3361 {
3362 /* first, unregister the target since it may become a base
3363 * hard disk which needs re-registration */
3364 rc2 = target->mVirtualBox->
3365 unregisterHardDisk2 (target, false /* aSaveSettings */);
3366 AssertComRC (rc2);
3367
3368 /* then, reparent it and disconnect the deleted branch at
3369 * both ends (chain->parent() is source's parent) */
3370 target->mParent->removeDependentChild (target);
3371 target->mParent = chain->parent();
3372 if (!target->mParent.isNull())
3373 {
3374 target->mParent->addDependentChild (target);
3375 target->mParent->removeDependentChild (source);
3376 source->mParent.setNull();
3377 }
3378 else
3379 {
3380 target->mVirtualBox->addDependentChild (target);
3381 target->mVirtualBox->removeDependentChild (source);
3382 }
3383
3384 /* then, register again */
3385 rc2 = target->mVirtualBox->
3386 registerHardDisk2 (target, false /* aSaveSettings */);
3387 AssertComRC (rc2);
3388 }
3389 else
3390 {
3391 Assert (target->children().size() == 1);
3392 HardDisk2 *targetChild = target->children().front();
3393
3394 /* disconnect the deleted branch at the elder end */
3395 target->removeDependentChild (targetChild);
3396 targetChild->mParent.setNull();
3397
3398 const List &children = chain->children();
3399
3400 /* reparent source's chidren and disconnect the deleted
3401 * branch at the younger end m*/
3402 if (children.size() > 0)
3403 {
3404 /* obey {parent,child} lock order */
3405 AutoWriteLock sourceLock (source);
3406
3407 for (List::const_iterator it = children.begin();
3408 it != children.end(); ++ it)
3409 {
3410 AutoWriteLock childLock (*it);
3411
3412 (*it)->mParent = target;
3413 (*it)->mParent->addDependentChild (*it);
3414 source->removeDependentChild (*it);
3415 }
3416 }
3417 }
3418
3419 /* try to save the hard disk registry */
3420 rc = that->mVirtualBox->saveSettings();
3421
3422 if (SUCCEEDED (rc))
3423 {
3424 /* unregister and uninitialize all hard disks in the chain
3425 * but the target */
3426
3427 for (MergeChain::iterator it = chain->begin();
3428 it != chain->end();)
3429 {
3430 if (*it == chain->target())
3431 {
3432 ++ it;
3433 continue;
3434 }
3435
3436 rc2 = (*it)->mVirtualBox->
3437 unregisterHardDisk2 (*it, false /* aSaveSettings */);
3438 AssertComRC (rc2);
3439
3440 /* now, uninitialize the deleted hard disk (note that
3441 * due to the Deleting state, uninit() will not touch
3442 * the parent-child relationship so we need to
3443 * uninitialize each disk individually) */
3444
3445 /* note that the operation initiator hard disk (which is
3446 * normally also the source hard disk) is a special case
3447 * -- there is one more caller added by Task to it which
3448 * we must release. Also, if we are in sync mode, the
3449 * caller may still hold an AutoCaller instance for it
3450 * and therefore we cannot uninit() it (it's therefore
3451 * the caller's responsibility) */
3452 if (*it == that)
3453 task->autoCaller.release();
3454
3455 /* release the caller added by MergeChain before
3456 * uninit() */
3457 (*it)->releaseCaller();
3458
3459 if (isAsync || *it != that)
3460 (*it)->uninit();
3461
3462 /* delete (to prevent uninitialization in MergeChain
3463 * dtor) and advance to the next item */
3464 it = chain->erase (it);
3465 }
3466
3467 /* Note that states of all other hard disks (target, parent,
3468 * children) will be restored by the MergeChain dtor */
3469 }
3470 else
3471 {
3472 /* too bad if we fail, but we'll need to rollback everything
3473 * we did above to at least keep the HD tree in sync with
3474 * the current registry on disk */
3475
3476 saveSettingsFailed = true;
3477
3478 /// @todo NEWMEDIA implement a proper undo
3479
3480 AssertFailed();
3481 }
3482 }
3483
3484 if (FAILED (rc))
3485 {
3486 /* Here we come if either VDMerge() failed (in which case we
3487 * assume that it tried to do everything to make a further
3488 * retry possible -- e.g. not deleted intermediate hard disks
3489 * and so on) or VirtualBox::saveSettings() failed (where we
3490 * should have the original tree but with intermediate storage
3491 * units deleted by VDMerge()). We have to only restore states
3492 * (through the MergeChain dtor) unless we are run synchronously
3493 * in which case it's the responsibility of the caller as stated
3494 * in the mergeTo() docs. The latter also implies that we
3495 * don't own the merge chain, so release it in this case. */
3496
3497 if (!isAsync)
3498 task->d.chain.release();
3499
3500 NOREF (saveSettingsFailed);
3501 }
3502
3503 break;
3504 }
3505
3506 ////////////////////////////////////////////////////////////////////////
3507
3508 case Task::Delete:
3509 {
3510 /* The lock is also used as a signal from the task initiator (which
3511 * releases it only after RTThreadCreate()) that we can start the job */
3512 AutoWriteLock thatLock (that);
3513
3514 try
3515 {
3516 PVBOXHDD hdd;
3517 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3518 ComAssertRCThrow (vrc, E_FAIL);
3519
3520 Utf8Str format (that->mm.format);
3521 Utf8Str location (that->m.locationFull);
3522
3523 /* unlock before the potentially lengthy operation */
3524 Assert (that->m.state == MediaState_Deleting);
3525 thatLock.leave();
3526
3527 try
3528 {
3529 vrc = VDOpen (hdd, format, location,
3530 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3531 NULL);
3532 if (RT_SUCCESS (vrc))
3533 vrc = VDClose (hdd, true /* fDelete */);
3534
3535 if (RT_FAILURE (vrc))
3536 {
3537 throw setError (E_FAIL,
3538 tr ("Could not delete the hard disk storage "
3539 "unit '%s'%s"),
3540 location.raw(), that->vdError (vrc).raw());
3541 }
3542
3543 }
3544 catch (HRESULT aRC) { rc = aRC; }
3545
3546 VDDestroy (hdd);
3547 }
3548 catch (HRESULT aRC) { rc = aRC; }
3549
3550 thatLock.maybeEnter();
3551
3552 /* go to the NotCreated state even on failure since the storage
3553 * may have been already partially deleted and cannot be used any
3554 * more. One will be able to manually re-open the storage if really
3555 * needed to re-register it. */
3556 that->m.state = MediaState_NotCreated;
3557
3558 break;
3559 }
3560
3561 default:
3562 AssertFailedReturn (VERR_GENERAL_FAILURE);
3563 }
3564
3565 /* complete the progress if run asynchronously */
3566 if (isAsync)
3567 {
3568 if (!task->progress.isNull())
3569 task->progress->notifyComplete (rc);
3570 }
3571 else
3572 {
3573 task->rc = rc;
3574 }
3575
3576 LogFlowFunc (("rc=%Rhrc\n", rc));
3577 LogFlowFuncLeave();
3578
3579 return VINF_SUCCESS;
3580
3581 /// @todo ugly hack, fix ComAssert... later
3582 #undef setError
3583}
3584
Note: See TracBrowser for help on using the repository browser.

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