VirtualBox

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

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

Main: Prototyped IHardDisk::autoReset and reset (including XML structure changes, backward compatible).

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