VirtualBox

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

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

Main/HardDisk: enable cancelling for all operations which take really long (creating, merging, cloning, ...) - everything except deleting.

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

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