VirtualBox

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

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

Main/IMedium,IHardDisk: fix error handling so that VBOX_E_FILE_ERROR is returned when the image is not present.

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