VirtualBox

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

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

Main: add read/write param to OpenHardDisk to allow for opening disk images during import without requiring write access

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