VirtualBox

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

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

Main: a different approach to opening images read-only for import which does not assert on empty UUIDs

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

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