VirtualBox

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

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

Main: add the Id keyword

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

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